feat: 裁剪组裁剪跟随选择组移动
This commit is contained in:
@@ -22,9 +22,7 @@ export class CreateBackgroundLayerCommand extends Command {
|
||||
|
||||
execute() {
|
||||
// 检查是否已经存在背景图层
|
||||
const existingBgLayer = this.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const existingBgLayer = this.layers.value.find((layer) => layer.isBackground);
|
||||
if (existingBgLayer) {
|
||||
console.warn("已存在背景层,不重复创建");
|
||||
return existingBgLayer.id;
|
||||
@@ -34,11 +32,7 @@ export class CreateBackgroundLayerCommand extends Command {
|
||||
const bgObject = this._createBackgroundObject();
|
||||
|
||||
// 将背景对象添加到图层中
|
||||
this.backgroundLayer.fabricObject = bgObject.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"type",
|
||||
]);
|
||||
this.backgroundLayer.fabricObject = bgObject.toObject(["id", "layerId", "type"]);
|
||||
|
||||
// 添加图层到最底部
|
||||
this.layers.value.push(this.backgroundLayer);
|
||||
@@ -54,9 +48,7 @@ export class CreateBackgroundLayerCommand extends Command {
|
||||
|
||||
undo() {
|
||||
// 从图层列表中删除背景图层
|
||||
const bgLayerIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const bgLayerIndex = this.layers.value.findIndex((layer) => layer.isBackground);
|
||||
if (bgLayerIndex !== -1) {
|
||||
this.layers.value.splice(bgLayerIndex, 1);
|
||||
}
|
||||
@@ -82,8 +74,7 @@ export class CreateBackgroundLayerCommand extends Command {
|
||||
|
||||
// 确保背景色为白色,如果没有设置或者是透明的话
|
||||
const backgroundColor =
|
||||
this.backgroundLayer.backgroundColor &&
|
||||
this.backgroundLayer.backgroundColor !== "transparent"
|
||||
this.backgroundLayer.backgroundColor && this.backgroundLayer.backgroundColor !== "transparent"
|
||||
? this.backgroundLayer.backgroundColor
|
||||
: "#ffffff";
|
||||
|
||||
@@ -139,10 +130,7 @@ export class UpdateBackgroundCommand extends Command {
|
||||
// 查找背景图层
|
||||
this.bgLayer = this.layers.value.find((layer) => layer.isBackground);
|
||||
this.oldBackgroundColor = this.oldColor;
|
||||
this.backgroundObject = findObjectById(
|
||||
this.canvas,
|
||||
this.bgLayer.fabricObject.id
|
||||
).object;
|
||||
this.backgroundObject = findObjectById(this.canvas, this.bgLayer.fabricObject.id).object;
|
||||
}
|
||||
|
||||
execute() {
|
||||
@@ -163,9 +151,7 @@ export class UpdateBackgroundCommand extends Command {
|
||||
this.backgroundColorValue.value = this.backgroundColor; // 设置背景颜色
|
||||
|
||||
// 生成缩略图
|
||||
this.canvasManager?.thumbnailManager?.generateLayerThumbnail?.(
|
||||
this.bgLayer.id
|
||||
);
|
||||
this.canvasManager?.thumbnailManager?.generateLayerThumbnail?.(this.bgLayer.id);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -186,9 +172,7 @@ export class UpdateBackgroundCommand extends Command {
|
||||
this.backgroundColorValue.value = this.oldBackgroundColor; // 恢复背景颜色
|
||||
|
||||
// 生成缩略图
|
||||
this.canvasManager?.thumbnailManager?.generateLayerThumbnail?.(
|
||||
this.bgLayer.id
|
||||
);
|
||||
this.canvasManager?.thumbnailManager?.generateLayerThumbnail?.(this.bgLayer.id);
|
||||
// 如果有旧颜色,恢复到旧颜色
|
||||
return true;
|
||||
}
|
||||
@@ -223,10 +207,7 @@ export class BackgroundSizeCommand extends Command {
|
||||
this.bgLayer = this.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
// 记录原尺寸
|
||||
this.backgroundObject = findObjectById(
|
||||
this.canvas,
|
||||
this.bgLayer.fabricObject.id
|
||||
).object;
|
||||
this.backgroundObject = findObjectById(this.canvas, this.bgLayer.fabricObject.id).object;
|
||||
|
||||
this.oldWidth = this.backgroundObject.width;
|
||||
this.oldHeight = this.backgroundObject.height;
|
||||
@@ -255,8 +236,7 @@ export class BackgroundSizeCommand extends Command {
|
||||
// 调整背景对象大小
|
||||
if (this.bgLayer && this.backgroundObject) {
|
||||
// 保持原有的背景颜色,如果没有设置则使用白色
|
||||
const currentFill =
|
||||
this.backgroundObject.fill || this.bgLayer.backgroundColor || "#ffffff";
|
||||
const currentFill = this.backgroundObject.fill || this.bgLayer.backgroundColor || "#ffffff";
|
||||
|
||||
this.backgroundObject.set({
|
||||
width: this.newWidth,
|
||||
@@ -343,10 +323,7 @@ export class BackgroundSizeWithScaleCommand extends Command {
|
||||
// 查找背景图层
|
||||
this.bgLayer = this.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
this.backgroundObject = findObjectById(
|
||||
this.canvas,
|
||||
this.bgLayer.fabricObject.id
|
||||
).object;
|
||||
this.backgroundObject = findObjectById(this.canvas, this.bgLayer.fabricObject.id).object;
|
||||
|
||||
// 计算缩放比例
|
||||
const scaleXRatio = this.newWidth / this.oldWidth;
|
||||
@@ -409,24 +386,21 @@ export class BackgroundSizeWithScaleCommand extends Command {
|
||||
// 统一缩放:使用平均值,保持相对比例的同时允许适度的形变
|
||||
this.uniformScale = Math.sqrt(scaleXRatio * scaleYRatio);
|
||||
this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2;
|
||||
this.offsetY =
|
||||
(this.newHeight - this.oldHeight * this.uniformScale) / 2;
|
||||
this.offsetY = (this.newHeight - this.oldHeight * this.uniformScale) / 2;
|
||||
break;
|
||||
|
||||
case "fit":
|
||||
// 适应模式:使用较小值,确保所有内容都在画布内,可能有留白
|
||||
this.uniformScale = Math.min(scaleXRatio, scaleYRatio);
|
||||
this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2;
|
||||
this.offsetY =
|
||||
(this.newHeight - this.oldHeight * this.uniformScale) / 2;
|
||||
this.offsetY = (this.newHeight - this.oldHeight * this.uniformScale) / 2;
|
||||
break;
|
||||
|
||||
case "fill":
|
||||
// 填充模式:使用较大值,填满画布,可能有部分内容被裁切
|
||||
this.uniformScale = Math.max(scaleXRatio, scaleYRatio);
|
||||
this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2;
|
||||
this.offsetY =
|
||||
(this.newHeight - this.oldHeight * this.uniformScale) / 2;
|
||||
this.offsetY = (this.newHeight - this.oldHeight * this.uniformScale) / 2;
|
||||
break;
|
||||
|
||||
case "stretch":
|
||||
@@ -441,8 +415,7 @@ export class BackgroundSizeWithScaleCommand extends Command {
|
||||
// 默认使用uniform模式
|
||||
this.uniformScale = Math.sqrt(scaleXRatio * scaleYRatio);
|
||||
this.offsetX = (this.newWidth - this.oldWidth * this.uniformScale) / 2;
|
||||
this.offsetY =
|
||||
(this.newHeight - this.oldHeight * this.uniformScale) / 2;
|
||||
this.offsetY = (this.newHeight - this.oldHeight * this.uniformScale) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,8 +435,7 @@ export class BackgroundSizeWithScaleCommand extends Command {
|
||||
// 调整背景对象大小和位置
|
||||
if (this.bgLayer && this.backgroundObject) {
|
||||
// 保持原有的背景颜色,如果没有设置则使用白色
|
||||
const currentFill =
|
||||
this.backgroundObject.fill || this.bgLayer.backgroundColor || "#ffffff";
|
||||
const currentFill = this.backgroundObject.fill || this.bgLayer.backgroundColor || "#ffffff";
|
||||
|
||||
this.backgroundObject.set({
|
||||
width: this.newWidth,
|
||||
@@ -480,12 +452,10 @@ export class BackgroundSizeWithScaleCommand extends Command {
|
||||
|
||||
// 计算基于原始画布的缩放比例
|
||||
const baseScaleX =
|
||||
this.newWidth /
|
||||
this.objectStates[0]?.obj._originalState?.baseCanvasWidth ||
|
||||
this.newWidth / this.objectStates[0]?.obj._originalState?.baseCanvasWidth ||
|
||||
this.newWidth / this.oldWidth;
|
||||
const baseScaleY =
|
||||
this.newHeight /
|
||||
this.objectStates[0]?.obj._originalState?.baseCanvasHeight ||
|
||||
this.newHeight / this.objectStates[0]?.obj._originalState?.baseCanvasHeight ||
|
||||
this.newHeight / this.oldHeight;
|
||||
|
||||
// 根据策略缩放所有非背景对象
|
||||
@@ -505,13 +475,11 @@ export class BackgroundSizeWithScaleCommand extends Command {
|
||||
const baseUniformScale = Math.sqrt(baseScaleX * baseScaleY);
|
||||
const baseOffsetX =
|
||||
(this.newWidth -
|
||||
(obj._originalState?.baseCanvasWidth || this.oldWidth) *
|
||||
baseUniformScale) /
|
||||
(obj._originalState?.baseCanvasWidth || this.oldWidth) * baseUniformScale) /
|
||||
2;
|
||||
const baseOffsetY =
|
||||
(this.newHeight -
|
||||
(obj._originalState?.baseCanvasHeight || this.oldHeight) *
|
||||
baseUniformScale) /
|
||||
(obj._originalState?.baseCanvasHeight || this.oldHeight) * baseUniformScale) /
|
||||
2;
|
||||
|
||||
obj.set({
|
||||
@@ -537,10 +505,7 @@ export class BackgroundSizeWithScaleCommand extends Command {
|
||||
this.canvas.setHeight(this.oldHeight);
|
||||
|
||||
// 如果使用 CanvasManager,通知它画布大小恢复
|
||||
if (
|
||||
this.canvasManager &&
|
||||
typeof this.canvasManager.updateCanvasSize === "function"
|
||||
) {
|
||||
if (this.canvasManager && typeof this.canvasManager.updateCanvasSize === "function") {
|
||||
this.canvasManager.updateCanvasSize(this.oldWidth, this.oldHeight);
|
||||
}
|
||||
|
||||
@@ -568,12 +533,9 @@ export class BackgroundSizeWithScaleCommand extends Command {
|
||||
const baseScaleX = this.oldWidth / originalState.baseCanvasWidth;
|
||||
const baseScaleY = this.oldHeight / originalState.baseCanvasHeight;
|
||||
const baseUniformScale = Math.sqrt(baseScaleX * baseScaleY);
|
||||
const baseOffsetX =
|
||||
(this.oldWidth - originalState.baseCanvasWidth * baseUniformScale) /
|
||||
2;
|
||||
const baseOffsetX = (this.oldWidth - originalState.baseCanvasWidth * baseUniformScale) / 2;
|
||||
const baseOffsetY =
|
||||
(this.oldHeight - originalState.baseCanvasHeight * baseUniformScale) /
|
||||
2;
|
||||
(this.oldHeight - originalState.baseCanvasHeight * baseUniformScale) / 2;
|
||||
|
||||
obj.set({
|
||||
left: originalState.left * baseUniformScale + baseOffsetX,
|
||||
|
||||
@@ -26,9 +26,7 @@ export class BrushSizeCommand extends BaseBrushCommand {
|
||||
super({
|
||||
...options,
|
||||
name: `设置笔刷大小: ${options.size}`,
|
||||
description: `将笔刷大小从 ${options.previousSize || "?"} 设为 ${
|
||||
options.size
|
||||
}`,
|
||||
description: `将笔刷大小从 ${options.previousSize || "?"} 设为 ${options.size}`,
|
||||
});
|
||||
|
||||
this.size = options.size;
|
||||
@@ -66,9 +64,7 @@ export class BrushColorCommand extends BaseBrushCommand {
|
||||
super({
|
||||
...options,
|
||||
name: `设置笔刷颜色: ${options.color}`,
|
||||
description: `将笔刷颜色从 ${options.previousColor || "?"} 设为 ${
|
||||
options.color
|
||||
}`,
|
||||
description: `将笔刷颜色从 ${options.previousColor || "?"} 设为 ${options.color}`,
|
||||
});
|
||||
|
||||
this.color = options.color;
|
||||
@@ -106,14 +102,11 @@ export class BrushOpacityCommand extends BaseBrushCommand {
|
||||
super({
|
||||
...options,
|
||||
name: `设置笔刷透明度: ${options.opacity}`,
|
||||
description: `将笔刷透明度从 ${options.previousOpacity || "?"} 设为 ${
|
||||
options.opacity
|
||||
}`,
|
||||
description: `将笔刷透明度从 ${options.previousOpacity || "?"} 设为 ${options.opacity}`,
|
||||
});
|
||||
|
||||
this.opacity = options.opacity;
|
||||
this.previousOpacity =
|
||||
options.previousOpacity || this.brushStore.state.opacity;
|
||||
this.previousOpacity = options.previousOpacity || this.brushStore.state.opacity;
|
||||
}
|
||||
|
||||
execute() {
|
||||
@@ -147,9 +140,7 @@ export class BrushTypeCommand extends BaseBrushCommand {
|
||||
super({
|
||||
...options,
|
||||
name: `设置笔刷类型: ${options.brushType}`,
|
||||
description: `将笔刷类型从 ${options.previousType || "?"} 设为 ${
|
||||
options.brushType
|
||||
}`,
|
||||
description: `将笔刷类型从 ${options.previousType || "?"} 设为 ${options.brushType}`,
|
||||
});
|
||||
|
||||
this.brushType = options.brushType;
|
||||
@@ -192,9 +183,7 @@ export class TextureCommand extends BaseBrushCommand {
|
||||
super({
|
||||
...options,
|
||||
name: options.enabled ? "启用笔刷材质" : "禁用笔刷材质",
|
||||
description: options.enabled
|
||||
? `启用材质: ${options.path || "[默认]"}`
|
||||
: "禁用笔刷材质",
|
||||
description: options.enabled ? `启用材质: ${options.path || "[默认]"}` : "禁用笔刷材质",
|
||||
});
|
||||
|
||||
this.enabled = options.enabled;
|
||||
@@ -260,9 +249,7 @@ export class BrushPresetCommand extends BaseBrushCommand {
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
const presetName =
|
||||
typeof options.preset === "object"
|
||||
? options.preset.name
|
||||
: `预设 ${options.preset}`;
|
||||
typeof options.preset === "object" ? options.preset.name : `预设 ${options.preset}`;
|
||||
|
||||
super({
|
||||
...options,
|
||||
@@ -303,14 +290,10 @@ export class BrushPresetCommand extends BaseBrushCommand {
|
||||
this.brushStore.applyPreset(this.preset);
|
||||
} else if (typeof this.preset === "object") {
|
||||
// 应用自定义预设对象
|
||||
if (this.preset.size !== undefined)
|
||||
this.brushStore.setBrushSize(this.preset.size);
|
||||
if (this.preset.color !== undefined)
|
||||
this.brushStore.setBrushColor(this.preset.color);
|
||||
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.size !== undefined) this.brushStore.setBrushSize(this.preset.size);
|
||||
if (this.preset.color !== undefined) this.brushStore.setBrushColor(this.preset.color);
|
||||
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) {
|
||||
this.brushStore.setTextureEnabled(this.preset.textureEnabled);
|
||||
@@ -542,8 +525,7 @@ export class TexturePresetCommand extends BaseBrushCommand {
|
||||
* @param {Object} options.brushManager 笔刷管理器实例
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
const presetName =
|
||||
typeof options.preset === "object" ? options.preset.name : options.preset;
|
||||
const presetName = typeof options.preset === "object" ? options.preset.name : options.preset;
|
||||
|
||||
super({
|
||||
...options,
|
||||
@@ -614,10 +596,7 @@ export class TextureUploadCommand extends BaseBrushCommand {
|
||||
});
|
||||
|
||||
this.file = options.file;
|
||||
this.name =
|
||||
options.name ||
|
||||
options.file?.name?.replace(/\.[^/.]+$/, "") ||
|
||||
"自定义纹理";
|
||||
this.name = options.name || options.file?.name?.replace(/\.[^/.]+$/, "") || "自定义纹理";
|
||||
this.category = options.category || "自定义材质";
|
||||
this.texturePresetManager = options.texturePresetManager;
|
||||
this.brushManager = options.brushManager;
|
||||
|
||||
@@ -24,8 +24,7 @@ export class ClearSelectionContentCommand extends Command {
|
||||
|
||||
this.oldLayer = [...this.layers.value]; // 获取原图层对象
|
||||
|
||||
const { layer } =
|
||||
findLayerRecursively(this.layers.value, this.targetLayerId) || {};
|
||||
const { layer } = findLayerRecursively(this.layers.value, this.targetLayerId) || {};
|
||||
|
||||
// 栅格化相关属性
|
||||
this.originalLayerBackup = null;
|
||||
@@ -37,17 +36,10 @@ export class ClearSelectionContentCommand extends Command {
|
||||
|
||||
// this.originalLayerObject = fabric.util.object.clone(originalLayerObject); // 获取原图层对象
|
||||
|
||||
this.oldLayerObjects = originalLayerObject.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
]); // 获取原图层的所有对象
|
||||
this.oldLayerObjects = originalLayerObject.toObject(["id", "layerId", "layerName"]); // 获取原图层的所有对象
|
||||
|
||||
this.rasterizedImage = null;
|
||||
this.targetLayer = findLayerRecursively(
|
||||
this.canvas,
|
||||
this.targetLayerId
|
||||
)?.layer;
|
||||
this.targetLayer = findLayerRecursively(this.canvas, this.targetLayerId)?.layer;
|
||||
|
||||
this.layerRasterized = false;
|
||||
|
||||
@@ -88,8 +80,7 @@ export class ClearSelectionContentCommand extends Command {
|
||||
}
|
||||
|
||||
// 确定目标图层
|
||||
const layerId =
|
||||
this.targetLayerId || this.layerManager.getActiveLayerId();
|
||||
const layerId = this.targetLayerId || this.layerManager.getActiveLayerId();
|
||||
this.targetLayer = this.layerManager.getLayerById(layerId);
|
||||
if (!this.targetLayer) {
|
||||
console.error("无法清除选区内容:目标图层无效");
|
||||
@@ -107,9 +98,7 @@ export class ClearSelectionContentCommand extends Command {
|
||||
}
|
||||
|
||||
// 创建反转选区(保留选区外的内容)
|
||||
const invertedSelection = await this._createInvertedSelection(
|
||||
this.selectionObject
|
||||
);
|
||||
const invertedSelection = await this._createInvertedSelection(this.selectionObject);
|
||||
if (!invertedSelection) {
|
||||
console.error("创建反转选区失败");
|
||||
return false;
|
||||
@@ -137,10 +126,7 @@ export class ClearSelectionContentCommand extends Command {
|
||||
|
||||
// 恢复原图层状态
|
||||
// 移除栅格化后的图像
|
||||
if (
|
||||
this.rasterizedImage &&
|
||||
this.canvas.getObjects().includes(this.rasterizedImage)
|
||||
) {
|
||||
if (this.rasterizedImage && this.canvas.getObjects().includes(this.rasterizedImage)) {
|
||||
this.canvas.remove(this.rasterizedImage);
|
||||
}
|
||||
|
||||
@@ -199,10 +185,7 @@ export class ClearSelectionContentCommand extends Command {
|
||||
let scaleFactor = this.baseResolutionScale;
|
||||
if (this.highResolutionEnabled) {
|
||||
const currentZoom = this.canvas.getZoom?.() || 1;
|
||||
scaleFactor = Math.max(
|
||||
scaleFactor || this.canvas?.getRetinaScaling?.(),
|
||||
currentZoom
|
||||
);
|
||||
scaleFactor = Math.max(scaleFactor || this.canvas?.getRetinaScaling?.(), currentZoom);
|
||||
scaleFactor = Math.min(scaleFactor, 3);
|
||||
}
|
||||
|
||||
@@ -286,9 +269,7 @@ export class ClearSelectionContentCommand extends Command {
|
||||
layerBounds.left + layerBounds.width
|
||||
} ${layerBounds.top} L ${layerBounds.left + layerBounds.width} ${
|
||||
layerBounds.top + layerBounds.height
|
||||
} L ${layerBounds.left} ${layerBounds.top + layerBounds.height} Z ${
|
||||
selectionObject.path
|
||||
}`;
|
||||
} L ${layerBounds.left} ${layerBounds.top + layerBounds.height} Z ${selectionObject.path}`;
|
||||
|
||||
const invertedPath = new fabric.Path(pathString, {
|
||||
fillRule: "evenodd",
|
||||
@@ -329,14 +310,8 @@ export class ClearSelectionContentCommand extends Command {
|
||||
if (!bounds) {
|
||||
bounds = { ...objBounds };
|
||||
} else {
|
||||
const right = Math.max(
|
||||
bounds.left + bounds.width,
|
||||
objBounds.left + objBounds.width
|
||||
);
|
||||
const bottom = Math.max(
|
||||
bounds.top + bounds.height,
|
||||
objBounds.top + objBounds.height
|
||||
);
|
||||
const right = Math.max(bounds.left + bounds.width, 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.top = Math.min(bounds.top, objBounds.top);
|
||||
@@ -370,15 +345,10 @@ export class ClearSelectionContentCommand extends Command {
|
||||
// 递归获取图层及其子图层的所有对象
|
||||
const collectLayerObjects = (currentLayer) => {
|
||||
// 处理图层的fabricObjects
|
||||
if (
|
||||
currentLayer.fabricObjects &&
|
||||
Array.isArray(currentLayer.fabricObjects)
|
||||
) {
|
||||
if (currentLayer.fabricObjects && Array.isArray(currentLayer.fabricObjects)) {
|
||||
currentLayer.fabricObjects.forEach((fabricObj) => {
|
||||
if (fabricObj && fabricObj.id) {
|
||||
const realObject = canvasObjects.find(
|
||||
(canvasObj) => canvasObj.id === fabricObj.id
|
||||
);
|
||||
const realObject = canvasObjects.find((canvasObj) => canvasObj.id === fabricObj.id);
|
||||
if (realObject && realObject.visible !== false) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -420,16 +390,12 @@ export class ClearSelectionContentCommand extends Command {
|
||||
}
|
||||
|
||||
// 移除栅格化后的图像
|
||||
if (
|
||||
this.rasterizedImage &&
|
||||
this.canvas.getObjects().includes(this.rasterizedImage)
|
||||
) {
|
||||
if (this.rasterizedImage && this.canvas.getObjects().includes(this.rasterizedImage)) {
|
||||
this.canvas.remove(this.rasterizedImage);
|
||||
}
|
||||
|
||||
// 恢复图层的fabricObjects
|
||||
this.targetLayer.fabricObjects =
|
||||
this.originalLayerBackup.fabricObjects || [];
|
||||
this.targetLayer.fabricObjects = this.originalLayerBackup.fabricObjects || [];
|
||||
|
||||
// 重新创建并添加对象到画布
|
||||
if (this.targetLayer.fabricObjects.length > 0) {
|
||||
|
||||
@@ -109,10 +109,7 @@ export class CompositeCommand extends Command {
|
||||
|
||||
console.log(`✅ 子命令执行成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 子命令执行失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
console.error(`❌ 子命令执行失败: ${command.constructor.name}`, error);
|
||||
|
||||
// 执行失败时,撤销已执行的命令
|
||||
await this._rollbackExecutedCommands();
|
||||
@@ -142,9 +139,7 @@ export class CompositeCommand extends Command {
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`↩️ 开始撤销复合命令,共 ${this.executedCommands.length} 个子命令`
|
||||
);
|
||||
console.log(`↩️ 开始撤销复合命令,共 ${this.executedCommands.length} 个子命令`);
|
||||
|
||||
try {
|
||||
// 逆序撤销已执行的命令
|
||||
@@ -164,10 +159,7 @@ export class CompositeCommand extends Command {
|
||||
results.push(finalResult);
|
||||
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
|
||||
// 撤销失败不中断整个撤销过程,但要记录错误
|
||||
}
|
||||
} else {
|
||||
@@ -203,10 +195,7 @@ export class CompositeCommand extends Command {
|
||||
}
|
||||
console.log(`✅ 子命令回滚成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 子命令回滚失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
console.error(`❌ 子命令回滚失败: ${command.constructor.name}`, error);
|
||||
// 回滚失败不中断整个回滚过程
|
||||
}
|
||||
}
|
||||
@@ -238,9 +227,7 @@ export class CompositeCommand extends Command {
|
||||
commandCount: this.commands.length,
|
||||
executedCount: this.executedCommands.length,
|
||||
isExecuting: this.isExecuting,
|
||||
subCommands: this.commands.map((cmd) =>
|
||||
cmd.getInfo ? cmd.getInfo() : cmd.constructor.name
|
||||
),
|
||||
subCommands: this.commands.map((cmd) => (cmd.getInfo ? cmd.getInfo() : cmd.constructor.name)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,9 +85,7 @@ export class CrossLevelMoveCommand extends Command {
|
||||
this.layers.value.forEach((layer) => {
|
||||
if (layer.fabricObject) {
|
||||
states[layer.id] = {
|
||||
isInCanvas: this.canvas
|
||||
? this.canvas.contains(layer.fabricObject)
|
||||
: false,
|
||||
isInCanvas: this.canvas ? this.canvas.contains(layer.fabricObject) : false,
|
||||
parentGroupId: null,
|
||||
visible: layer.fabricObject.visible,
|
||||
};
|
||||
@@ -127,11 +125,7 @@ export class CrossLevelMoveCommand extends Command {
|
||||
* 验证移动的有效性
|
||||
*/
|
||||
validateMove() {
|
||||
const layer = this.findLayer(
|
||||
this.layerId,
|
||||
this.fromContainerType,
|
||||
this.fromParentId
|
||||
);
|
||||
const layer = this.findLayer(this.layerId, this.fromContainerType, this.fromParentId);
|
||||
|
||||
if (!layer) {
|
||||
throw new Error(`找不到要移动的图层: ${this.layerId}`);
|
||||
@@ -153,9 +147,7 @@ export class CrossLevelMoveCommand extends Command {
|
||||
|
||||
// 检查目标父图层是否为组
|
||||
if (this.toContainerType === "child") {
|
||||
const targetParent = this.layers.value.find(
|
||||
(layer) => layer.id === this.toParentId
|
||||
);
|
||||
const targetParent = this.layers.value.find((layer) => layer.id === this.toParentId);
|
||||
if (!targetParent || !isGroupLayer(targetParent)) {
|
||||
throw new Error("目标图层不是组图层");
|
||||
}
|
||||
@@ -198,15 +190,9 @@ export class CrossLevelMoveCommand extends Command {
|
||||
|
||||
try {
|
||||
// 根据移动类型执行对应操作
|
||||
if (
|
||||
this.fromContainerType === "root" &&
|
||||
this.toContainerType === "child"
|
||||
) {
|
||||
if (this.fromContainerType === "root" && this.toContainerType === "child") {
|
||||
this.moveRootToGroup(draggedLayer);
|
||||
} else if (
|
||||
this.fromContainerType === "child" &&
|
||||
this.toContainerType === "root"
|
||||
) {
|
||||
} else if (this.fromContainerType === "child" && this.toContainerType === "root") {
|
||||
this.moveGroupToRoot(draggedLayer);
|
||||
} else if (
|
||||
this.fromContainerType === "child" &&
|
||||
@@ -228,6 +214,17 @@ export class CrossLevelMoveCommand extends Command {
|
||||
await this.layerManager?.updateLayersObjectsInteractivity();
|
||||
this.canvas?.renderAll();
|
||||
|
||||
// 生成缩略图
|
||||
this.fromParentId &&
|
||||
this.layerManager?.canvasManager?.thumbnailManager?.generateLayerThumbnail?.(
|
||||
this.fromParentId
|
||||
);
|
||||
|
||||
this.toParentId &&
|
||||
this.layerManager?.canvasManager?.thumbnailManager?.generateLayerThumbnail?.(
|
||||
this.toParentId
|
||||
);
|
||||
|
||||
console.log("✅ 跨层级移动命令执行成功");
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -266,7 +263,6 @@ export class CrossLevelMoveCommand extends Command {
|
||||
this.restoreFabricObjectStates(this.beforeState.fabricObjects);
|
||||
|
||||
await this.layerManager?.updateLayersObjectsInteractivity();
|
||||
this.canvas?.renderAll();
|
||||
// 刷新画布
|
||||
this.canvas?.renderAll();
|
||||
console.log("✅ 跨层级移动命令撤销成功");
|
||||
@@ -299,9 +295,7 @@ export class CrossLevelMoveCommand extends Command {
|
||||
|
||||
// 根据图层的parentId重新放置
|
||||
if (layer.parentId) {
|
||||
const parentLayer = this.layers.value.find(
|
||||
(l) => l.id === layer.parentId
|
||||
);
|
||||
const parentLayer = this.layers.value.find((l) => l.id === layer.parentId);
|
||||
if (
|
||||
parentLayer &&
|
||||
parentLayer.fabricObject &&
|
||||
@@ -396,14 +390,9 @@ export class CrossLevelMoveCommand extends Command {
|
||||
layer.children = layerData.children
|
||||
.map((childData) => {
|
||||
// 从原始图层数组中查找子图层
|
||||
let childLayer = originalLayers.find(
|
||||
(l) => l.id === childData.id
|
||||
);
|
||||
let childLayer = originalLayers.find((l) => l.id === childData.id);
|
||||
if (!childLayer) {
|
||||
childLayer = this.findLayerInOriginalStructure(
|
||||
childData.id,
|
||||
originalLayers
|
||||
);
|
||||
childLayer = this.findLayerInOriginalStructure(childData.id, originalLayers);
|
||||
}
|
||||
if (childLayer) {
|
||||
// 更新子图层属性
|
||||
@@ -465,9 +454,7 @@ export class CrossLevelMoveCommand extends Command {
|
||||
// 根据目标状态放置对象
|
||||
if (targetState.parentGroupId) {
|
||||
// 应该在特定组中
|
||||
const parentLayer = this.layers.value.find(
|
||||
(l) => l.id === targetState.parentGroupId
|
||||
);
|
||||
const parentLayer = this.layers.value.find((l) => l.id === targetState.parentGroupId);
|
||||
if (
|
||||
parentLayer &&
|
||||
parentLayer.fabricObject &&
|
||||
@@ -532,12 +519,7 @@ export class CrossLevelMoveCommand extends Command {
|
||||
*/
|
||||
safeRemoveFromContainer(fabricObject, container, containerName) {
|
||||
return this.safeFabricOperation(() => {
|
||||
if (
|
||||
container &&
|
||||
container.remove &&
|
||||
container.contains &&
|
||||
container.contains(fabricObject)
|
||||
) {
|
||||
if (container && container.remove && container.contains && container.contains(fabricObject)) {
|
||||
container.remove(fabricObject);
|
||||
console.log(`✅ 成功从${containerName}移除对象`);
|
||||
return true;
|
||||
@@ -556,11 +538,7 @@ export class CrossLevelMoveCommand extends Command {
|
||||
// 从所有组中移除
|
||||
this.layers.value.forEach((layer) => {
|
||||
if (layer.fabricObject && layer.fabricObject.type === "group") {
|
||||
this.safeRemoveFromContainer(
|
||||
fabricObject,
|
||||
layer.fabricObject,
|
||||
`组${layer.id}`
|
||||
);
|
||||
this.safeRemoveFromContainer(fabricObject, layer.fabricObject, `组${layer.id}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -575,9 +553,7 @@ export class CrossLevelMoveCommand extends Command {
|
||||
newIndex: this.newIndex,
|
||||
});
|
||||
|
||||
const targetParent = this.layers.value.find(
|
||||
(layer) => layer.id === this.toParentId
|
||||
);
|
||||
const targetParent = this.layers.value.find((layer) => layer.id === this.toParentId);
|
||||
if (!targetParent) {
|
||||
throw new Error(`找不到目标父图层: ${this.toParentId}`);
|
||||
}
|
||||
@@ -588,9 +564,7 @@ export class CrossLevelMoveCommand extends Command {
|
||||
}
|
||||
|
||||
// 从顶级图层数组中移除
|
||||
const rootIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.id === draggedLayer.id
|
||||
);
|
||||
const rootIndex = this.layers.value.findIndex((layer) => layer.id === draggedLayer.id);
|
||||
if (rootIndex !== -1) {
|
||||
this.layers.value.splice(rootIndex, 1);
|
||||
console.log(`🗑️ 从顶级图层数组中移除图层 ${draggedLayer.id}`);
|
||||
@@ -599,20 +573,14 @@ export class CrossLevelMoveCommand extends Command {
|
||||
// 更新图层关系
|
||||
draggedLayer.parentId = this.toParentId;
|
||||
targetParent.children.splice(this.newIndex, 0, draggedLayer);
|
||||
console.log(
|
||||
`📂 将图层 ${draggedLayer.id} 添加到父图层 ${this.toParentId} 的children中`
|
||||
);
|
||||
console.log(`📂 将图层 ${draggedLayer.id} 添加到父图层 ${this.toParentId} 的children中`);
|
||||
|
||||
// 处理fabric对象的层级关系
|
||||
if (draggedLayer.fabricObject && targetParent.fabricObject) {
|
||||
console.log(`🎨 处理fabric对象层级关系`);
|
||||
|
||||
// 从画布中移除
|
||||
this.safeRemoveFromContainer(
|
||||
draggedLayer.fabricObject,
|
||||
this.canvas,
|
||||
"画布"
|
||||
);
|
||||
this.safeRemoveFromContainer(draggedLayer.fabricObject, this.canvas, "画布");
|
||||
|
||||
// 添加到父组
|
||||
this.safeAddToContainer(
|
||||
@@ -635,22 +603,16 @@ export class CrossLevelMoveCommand extends Command {
|
||||
newIndex: this.newIndex,
|
||||
});
|
||||
|
||||
const sourceParent = this.layers.value.find(
|
||||
(layer) => layer.id === this.fromParentId
|
||||
);
|
||||
const sourceParent = this.layers.value.find((layer) => layer.id === this.fromParentId);
|
||||
if (!sourceParent || !sourceParent.children) {
|
||||
throw new Error(`找不到源父图层或其children数组: ${this.fromParentId}`);
|
||||
}
|
||||
|
||||
// 从源父图层的children中移除
|
||||
const childIndex = sourceParent.children.findIndex(
|
||||
(child) => child.id === draggedLayer.id
|
||||
);
|
||||
const childIndex = sourceParent.children.findIndex((child) => child.id === draggedLayer.id);
|
||||
if (childIndex !== -1) {
|
||||
sourceParent.children.splice(childIndex, 1);
|
||||
console.log(
|
||||
`🗑️ 从父图层 ${this.fromParentId} 的children中移除图层 ${draggedLayer.id}`
|
||||
);
|
||||
console.log(`🗑️ 从父图层 ${this.fromParentId} 的children中移除图层 ${draggedLayer.id}`);
|
||||
}
|
||||
|
||||
// 处理fabric对象的层级关系
|
||||
@@ -675,9 +637,7 @@ export class CrossLevelMoveCommand extends Command {
|
||||
// 计算在顶级图层中的插入位置
|
||||
const targetIndex = Math.min(this.newIndex, this.layers.value.length);
|
||||
this.layers.value.splice(targetIndex, 0, draggedLayer);
|
||||
console.log(
|
||||
`📂 将图层 ${draggedLayer.id} 添加到顶级图层数组位置 ${targetIndex}`
|
||||
);
|
||||
console.log(`📂 将图层 ${draggedLayer.id} 添加到顶级图层数组位置 ${targetIndex}`);
|
||||
|
||||
console.log("✅ 组内图层移动到顶级完成");
|
||||
}
|
||||
@@ -693,26 +653,18 @@ export class CrossLevelMoveCommand extends Command {
|
||||
newIndex: this.newIndex,
|
||||
});
|
||||
|
||||
const sourceParent = this.layers.value.find(
|
||||
(layer) => layer.id === this.fromParentId
|
||||
);
|
||||
const targetParent = this.layers.value.find(
|
||||
(layer) => layer.id === this.toParentId
|
||||
);
|
||||
const sourceParent = this.layers.value.find((layer) => layer.id === this.fromParentId);
|
||||
const targetParent = this.layers.value.find((layer) => layer.id === this.toParentId);
|
||||
|
||||
if (!sourceParent || !targetParent) {
|
||||
throw new Error("找不到源父图层或目标父图层");
|
||||
}
|
||||
|
||||
// 从源父图层中移除
|
||||
const childIndex = sourceParent.children.findIndex(
|
||||
(child) => child.id === draggedLayer.id
|
||||
);
|
||||
const childIndex = sourceParent.children.findIndex((child) => child.id === draggedLayer.id);
|
||||
if (childIndex !== -1) {
|
||||
sourceParent.children.splice(childIndex, 1);
|
||||
console.log(
|
||||
`🗑️ 从源父图层 ${this.fromParentId} 中移除图层 ${draggedLayer.id}`
|
||||
);
|
||||
console.log(`🗑️ 从源父图层 ${this.fromParentId} 中移除图层 ${draggedLayer.id}`);
|
||||
|
||||
// 从源父组的fabricObject中移除
|
||||
if (draggedLayer.fabricObject && sourceParent.fabricObject) {
|
||||
@@ -726,9 +678,7 @@ export class CrossLevelMoveCommand extends Command {
|
||||
|
||||
// 更新图层的parentId
|
||||
draggedLayer.parentId = this.toParentId;
|
||||
console.log(
|
||||
`🔗 更新图层 ${draggedLayer.id} 的parentId为 ${this.toParentId}`
|
||||
);
|
||||
console.log(`🔗 更新图层 ${draggedLayer.id} 的parentId为 ${this.toParentId}`);
|
||||
|
||||
// 确保目标父图层有children数组
|
||||
if (!targetParent.children) {
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
createLayer,
|
||||
findInChildLayers,
|
||||
LayerType,
|
||||
} from "../utils/layerHelper.js";
|
||||
import { createLayer, findInChildLayers, LayerType } from "../utils/layerHelper.js";
|
||||
import { createRasterizedImage } from "../utils/selectionToImage.js";
|
||||
import { CompositeCommand, Command } from "./Command.js";
|
||||
import { CreateImageLayerCommand } from "./LayerCommands.js";
|
||||
@@ -56,9 +52,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
const selectionObject = this.selectionManager.getSelectionObject();
|
||||
if (selectionObject) {
|
||||
try {
|
||||
this._clonedSelectionObject = await this._cloneObject(
|
||||
selectionObject
|
||||
);
|
||||
this._clonedSelectionObject = await this._cloneObject(selectionObject);
|
||||
console.log("套索抠图:选区对象已克隆保存");
|
||||
} catch (error) {
|
||||
console.error("套索抠图:克隆选区对象失败:", error);
|
||||
@@ -294,10 +288,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
await command.undo();
|
||||
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
|
||||
// 子命令撤销失败不中断整个撤销过程
|
||||
}
|
||||
}
|
||||
@@ -361,16 +352,11 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
// 递归获取图层及其子图层的所有对象
|
||||
const collectLayerObjects = (currentLayer) => {
|
||||
// 处理图层的fabricObjects
|
||||
if (
|
||||
currentLayer.fabricObjects &&
|
||||
Array.isArray(currentLayer.fabricObjects)
|
||||
) {
|
||||
if (currentLayer.fabricObjects && Array.isArray(currentLayer.fabricObjects)) {
|
||||
currentLayer.fabricObjects.forEach((fabricRef) => {
|
||||
if (fabricRef && fabricRef.id) {
|
||||
// 从画布中查找真实的对象
|
||||
const realObject = canvasObjects.find(
|
||||
(obj) => obj.id === fabricRef.id
|
||||
);
|
||||
const realObject = canvasObjects.find((obj) => obj.id === fabricRef.id);
|
||||
if (realObject && realObject.visible) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -380,9 +366,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
|
||||
// 处理单个fabricObject(背景图层等)
|
||||
if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
|
||||
const realObject = canvasObjects.find(
|
||||
(obj) => obj.id === currentLayer.fabricObject.id
|
||||
);
|
||||
const realObject = canvasObjects.find((obj) => obj.id === currentLayer.fabricObject.id);
|
||||
if (realObject && realObject.visible) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -413,11 +397,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
* @returns {String} 抠图结果的DataURL
|
||||
* @private
|
||||
*/
|
||||
async _performCutoutWithRasterized(
|
||||
sourceObjects,
|
||||
selectionObject,
|
||||
selectionBounds
|
||||
) {
|
||||
async _performCutoutWithRasterized(sourceObjects, selectionObject, selectionBounds) {
|
||||
try {
|
||||
console.log("=== 开始使用createRasterizedImage执行抠图 ===");
|
||||
console.log(`源对象数量: ${sourceObjects.length}`);
|
||||
@@ -456,10 +436,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
|
||||
// 如果createRasterizedImage失败,回退到原始方法
|
||||
console.log("⚠️ 回退到原始抠图方法...");
|
||||
return await this._performCutout(
|
||||
{ fabricObjects: sourceObjects },
|
||||
selectionObject
|
||||
);
|
||||
return await this._performCutout({ fabricObjects: sourceObjects }, selectionObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,9 +467,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
|
||||
try {
|
||||
// 收集源图层中的可见对象
|
||||
const visibleObjects = sourceLayer.fabricObjects.filter(
|
||||
(obj) => obj.visible
|
||||
);
|
||||
const visibleObjects = sourceLayer.fabricObjects.filter((obj) => obj.visible);
|
||||
|
||||
if (visibleObjects.length === 0) {
|
||||
throw new Error("源图层没有可见对象");
|
||||
@@ -545,10 +520,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
let highResolutionScale = 1;
|
||||
if (this.highResolutionEnabled) {
|
||||
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
highResolutionScale = Math.max(
|
||||
this.baseResolutionScale,
|
||||
devicePixelRatio * 2
|
||||
);
|
||||
highResolutionScale = Math.max(this.baseResolutionScale, devicePixelRatio * 2);
|
||||
}
|
||||
|
||||
// 创建用于导出的高分辨率canvas
|
||||
@@ -559,12 +531,8 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
colorSpace: "srgb",
|
||||
});
|
||||
|
||||
const actualWidth = Math.round(
|
||||
renderBounds.width * highResolutionScale
|
||||
);
|
||||
const actualHeight = Math.round(
|
||||
renderBounds.height * highResolutionScale
|
||||
);
|
||||
const actualWidth = Math.round(renderBounds.width * highResolutionScale);
|
||||
const actualHeight = Math.round(renderBounds.height * highResolutionScale);
|
||||
|
||||
exportCanvas.width = actualWidth;
|
||||
exportCanvas.height = actualHeight;
|
||||
@@ -773,17 +741,14 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "polygon") {
|
||||
fabric.Polygon.fromObject(
|
||||
this.serializedSelectionObject,
|
||||
(polygon) => {
|
||||
if (polygon) {
|
||||
console.log("多边形选区对象反序列化成功");
|
||||
resolve(polygon);
|
||||
} else {
|
||||
reject(new Error("多边形选区对象反序列化失败"));
|
||||
}
|
||||
fabric.Polygon.fromObject(this.serializedSelectionObject, (polygon) => {
|
||||
if (polygon) {
|
||||
console.log("多边形选区对象反序列化成功");
|
||||
resolve(polygon);
|
||||
} else {
|
||||
reject(new Error("多边形选区对象反序列化失败"));
|
||||
}
|
||||
);
|
||||
});
|
||||
} else if (objectType === "rect") {
|
||||
fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => {
|
||||
if (rect) {
|
||||
@@ -794,30 +759,24 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "ellipse" || objectType === "circle") {
|
||||
fabric.Ellipse.fromObject(
|
||||
this.serializedSelectionObject,
|
||||
(ellipse) => {
|
||||
if (ellipse) {
|
||||
console.log("椭圆选区对象反序列化成功");
|
||||
resolve(ellipse);
|
||||
} else {
|
||||
reject(new Error("椭圆选区对象反序列化失败"));
|
||||
}
|
||||
fabric.Ellipse.fromObject(this.serializedSelectionObject, (ellipse) => {
|
||||
if (ellipse) {
|
||||
console.log("椭圆选区对象反序列化成功");
|
||||
resolve(ellipse);
|
||||
} else {
|
||||
reject(new Error("椭圆选区对象反序列化失败"));
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// 通用对象反序列化
|
||||
fabric.util.enlivenObjects(
|
||||
[this.serializedSelectionObject],
|
||||
(objects) => {
|
||||
if (objects && objects.length > 0) {
|
||||
console.log("通用选区对象反序列化成功");
|
||||
resolve(objects[0]);
|
||||
} else {
|
||||
reject(new Error("通用选区对象反序列化失败"));
|
||||
}
|
||||
fabric.util.enlivenObjects([this.serializedSelectionObject], (objects) => {
|
||||
if (objects && objects.length > 0) {
|
||||
console.log("通用选区对象反序列化成功");
|
||||
resolve(objects[0]);
|
||||
} else {
|
||||
reject(new Error("通用选区对象反序列化失败"));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
createLayer,
|
||||
findInChildLayers,
|
||||
LayerType,
|
||||
} from "../utils/layerHelper.js";
|
||||
import { createLayer, findInChildLayers, LayerType } from "../utils/layerHelper.js";
|
||||
import { createRasterizedImage } from "../utils/selectionToImage.js";
|
||||
import { CompositeCommand, Command } from "./Command.js";
|
||||
import { CreateImageLayerCommand } from "./LayerCommands.js";
|
||||
@@ -59,9 +55,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
const selectionObject = this.selectionManager.getSelectionObject();
|
||||
if (selectionObject) {
|
||||
try {
|
||||
this._clonedSelectionObject = await this._cloneObject(
|
||||
selectionObject
|
||||
);
|
||||
this._clonedSelectionObject = await this._cloneObject(selectionObject);
|
||||
console.log("剪切选区:选区对象已克隆保存");
|
||||
} catch (error) {
|
||||
console.error("剪切选区:克隆选区对象失败:", error);
|
||||
@@ -132,11 +126,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
}
|
||||
|
||||
// 步骤2: 对原图层进行栅格化处理,移除选区内容
|
||||
await this._rasterizeOriginalLayerWithCutout(
|
||||
sourceLayer,
|
||||
sourceObjects,
|
||||
selectionObject
|
||||
);
|
||||
await this._rasterizeOriginalLayerWithCutout(sourceLayer, sourceObjects, selectionObject);
|
||||
|
||||
// 步骤3: 创建图像图层命令
|
||||
const createImageLayerCmd = new CreateImageLayerCommand({
|
||||
@@ -246,10 +236,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
|
||||
// 子命令撤销失败不中断整个撤销过程
|
||||
}
|
||||
}
|
||||
@@ -313,17 +300,14 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
await this._restoreObjectsFromBackup(sourceLayer);
|
||||
} else {
|
||||
// 备用方案:恢复原始的fabricObjects数组
|
||||
sourceLayer.fabricObjects =
|
||||
this.originalLayerBackup.fabricObjects || [];
|
||||
sourceLayer.fabricObjects = this.originalLayerBackup.fabricObjects || [];
|
||||
|
||||
if (sourceLayer.fabricObjects.length > 0) {
|
||||
await this._restoreLayerObjects(sourceLayer);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`✅ 原图层状态恢复完成,恢复了 ${sourceLayer.fabricObjects.length} 个对象`
|
||||
);
|
||||
console.log(`✅ 原图层状态恢复完成,恢复了 ${sourceLayer.fabricObjects.length} 个对象`);
|
||||
} catch (error) {
|
||||
console.error("恢复原图层状态失败:", error);
|
||||
throw error;
|
||||
@@ -337,78 +321,68 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
*/
|
||||
async _restoreObjectsFromBackup(layer) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (
|
||||
!this.originalLayerObjectsData ||
|
||||
this.originalLayerObjectsData.length === 0
|
||||
) {
|
||||
if (!this.originalLayerObjectsData || this.originalLayerObjectsData.length === 0) {
|
||||
console.warn("没有对象备份数据");
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`开始从备份恢复 ${this.originalLayerObjectsData.length} 个对象...`
|
||||
);
|
||||
console.log(`开始从备份恢复 ${this.originalLayerObjectsData.length} 个对象...`);
|
||||
|
||||
// 使用fabric.util.enlivenObjects重建对象
|
||||
fabric.util.enlivenObjects(
|
||||
this.originalLayerObjectsData,
|
||||
(restoredObjects) => {
|
||||
try {
|
||||
let successCount = 0;
|
||||
fabric.util.enlivenObjects(this.originalLayerObjectsData, (restoredObjects) => {
|
||||
try {
|
||||
let successCount = 0;
|
||||
|
||||
restoredObjects.forEach((obj, index) => {
|
||||
if (obj) {
|
||||
// 确保对象有正确的属性
|
||||
obj.set({
|
||||
layerId: layer.id,
|
||||
layerName: layer.name,
|
||||
selectable: true,
|
||||
evented: true,
|
||||
visible: true,
|
||||
});
|
||||
restoredObjects.forEach((obj, index) => {
|
||||
if (obj) {
|
||||
// 确保对象有正确的属性
|
||||
obj.set({
|
||||
layerId: layer.id,
|
||||
layerName: layer.name,
|
||||
selectable: true,
|
||||
evented: true,
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// 添加到画布
|
||||
this.canvas.add(obj);
|
||||
// 添加到画布
|
||||
this.canvas.add(obj);
|
||||
|
||||
// 添加到图层的fabricObjects数组
|
||||
layer.fabricObjects.push(
|
||||
obj.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"parentId",
|
||||
"selectable",
|
||||
"evented",
|
||||
"visible",
|
||||
])
|
||||
);
|
||||
// 添加到图层的fabricObjects数组
|
||||
layer.fabricObjects.push(
|
||||
obj.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"parentId",
|
||||
"selectable",
|
||||
"evented",
|
||||
"visible",
|
||||
])
|
||||
);
|
||||
|
||||
successCount++;
|
||||
console.log(
|
||||
`恢复对象 ${index + 1}/${restoredObjects.length}: ${
|
||||
obj.id || obj.type
|
||||
}`
|
||||
);
|
||||
} else {
|
||||
console.warn(`对象 ${index + 1} 恢复失败`);
|
||||
}
|
||||
});
|
||||
successCount++;
|
||||
console.log(
|
||||
`恢复对象 ${index + 1}/${restoredObjects.length}: ${obj.id || obj.type}`
|
||||
);
|
||||
} else {
|
||||
console.warn(`对象 ${index + 1} 恢复失败`);
|
||||
}
|
||||
});
|
||||
|
||||
// 重新渲染画布
|
||||
this.canvas.renderAll();
|
||||
// 重新渲染画布
|
||||
this.canvas.renderAll();
|
||||
|
||||
console.log(
|
||||
`✅ 成功恢复了 ${successCount}/${this.originalLayerObjectsData.length} 个对象`
|
||||
);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error("处理恢复的对象时出错:", error);
|
||||
reject(error);
|
||||
}
|
||||
console.log(
|
||||
`✅ 成功恢复了 ${successCount}/${this.originalLayerObjectsData.length} 个对象`
|
||||
);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error("处理恢复的对象时出错:", error);
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("恢复对象时出错:", error);
|
||||
reject(error);
|
||||
@@ -474,11 +448,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
* @returns {fabric.Image} 抠图结果的fabric图像对象
|
||||
* @private
|
||||
*/
|
||||
async _performCutoutWithRasterized(
|
||||
sourceObjects,
|
||||
selectionObject,
|
||||
selectionBounds
|
||||
) {
|
||||
async _performCutoutWithRasterized(sourceObjects, selectionObject, selectionBounds) {
|
||||
try {
|
||||
console.log("=== 开始使用createRasterizedImage执行剪切抠图 ===");
|
||||
console.log(`源对象数量: ${sourceObjects.length}`);
|
||||
@@ -488,10 +458,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
let scaleFactor = this.baseResolutionScale;
|
||||
if (this.highResolutionEnabled) {
|
||||
const currentZoom = this.canvas.getZoom?.() || 1;
|
||||
scaleFactor = Math.max(
|
||||
scaleFactor || this.canvas?.getRetinaScaling?.(),
|
||||
currentZoom
|
||||
);
|
||||
scaleFactor = Math.max(scaleFactor || this.canvas?.getRetinaScaling?.(), currentZoom);
|
||||
scaleFactor = Math.min(scaleFactor, 3);
|
||||
}
|
||||
|
||||
@@ -535,15 +502,10 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
// 递归获取图层及其子图层的所有对象
|
||||
const collectLayerObjects = (currentLayer) => {
|
||||
// 处理图层的fabricObjects
|
||||
if (
|
||||
currentLayer.fabricObjects &&
|
||||
Array.isArray(currentLayer.fabricObjects)
|
||||
) {
|
||||
if (currentLayer.fabricObjects && Array.isArray(currentLayer.fabricObjects)) {
|
||||
currentLayer.fabricObjects.forEach((fabricObj) => {
|
||||
if (fabricObj && fabricObj.id) {
|
||||
const realObject = canvasObjects.find(
|
||||
(canvasObj) => canvasObj.id === fabricObj.id
|
||||
);
|
||||
const realObject = canvasObjects.find((canvasObj) => canvasObj.id === fabricObj.id);
|
||||
if (realObject && realObject.visible !== false) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -649,17 +611,14 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "polygon") {
|
||||
fabric.Polygon.fromObject(
|
||||
this.serializedSelectionObject,
|
||||
(polygon) => {
|
||||
if (polygon) {
|
||||
console.log("多边形选区对象反序列化成功");
|
||||
resolve(polygon);
|
||||
} else {
|
||||
reject(new Error("多边形选区对象反序列化失败"));
|
||||
}
|
||||
fabric.Polygon.fromObject(this.serializedSelectionObject, (polygon) => {
|
||||
if (polygon) {
|
||||
console.log("多边形选区对象反序列化成功");
|
||||
resolve(polygon);
|
||||
} else {
|
||||
reject(new Error("多边形选区对象反序列化失败"));
|
||||
}
|
||||
);
|
||||
});
|
||||
} else if (objectType === "rect") {
|
||||
fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => {
|
||||
if (rect) {
|
||||
@@ -670,30 +629,24 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "ellipse" || objectType === "circle") {
|
||||
fabric.Ellipse.fromObject(
|
||||
this.serializedSelectionObject,
|
||||
(ellipse) => {
|
||||
if (ellipse) {
|
||||
console.log("椭圆选区对象反序列化成功");
|
||||
resolve(ellipse);
|
||||
} else {
|
||||
reject(new Error("椭圆选区对象反序列化失败"));
|
||||
}
|
||||
fabric.Ellipse.fromObject(this.serializedSelectionObject, (ellipse) => {
|
||||
if (ellipse) {
|
||||
console.log("椭圆选区对象反序列化成功");
|
||||
resolve(ellipse);
|
||||
} else {
|
||||
reject(new Error("椭圆选区对象反序列化失败"));
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// 通用对象反序列化
|
||||
fabric.util.enlivenObjects(
|
||||
[this.serializedSelectionObject],
|
||||
(objects) => {
|
||||
if (objects && objects.length > 0) {
|
||||
console.log("通用选区对象反序列化成功");
|
||||
resolve(objects[0]);
|
||||
} else {
|
||||
reject(new Error("通用选区对象反序列化失败"));
|
||||
}
|
||||
fabric.util.enlivenObjects([this.serializedSelectionObject], (objects) => {
|
||||
if (objects && objects.length > 0) {
|
||||
console.log("通用选区对象反序列化成功");
|
||||
resolve(objects[0]);
|
||||
} else {
|
||||
reject(new Error("通用选区对象反序列化失败"));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -761,9 +714,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
|
||||
this.originalLayerRasterized = false; // 标记原图层是否已栅格化
|
||||
|
||||
console.log(
|
||||
`✅ 原图层状态已备份,对象数量: ${this.originalLayerObjectsData.length}`
|
||||
);
|
||||
console.log(`✅ 原图层状态已备份,对象数量: ${this.originalLayerObjectsData.length}`);
|
||||
} catch (error) {
|
||||
console.error("备份原图层状态失败:", error);
|
||||
throw error;
|
||||
@@ -777,11 +728,7 @@ export class CutSelectionToNewLayerCommand extends CompositeCommand {
|
||||
* @param {Object} selectionObject 选区对象
|
||||
* @private
|
||||
*/
|
||||
async _rasterizeOriginalLayerWithCutout(
|
||||
sourceLayer,
|
||||
sourceObjects,
|
||||
selectionObject
|
||||
) {
|
||||
async _rasterizeOriginalLayerWithCutout(sourceLayer, sourceObjects, selectionObject) {
|
||||
try {
|
||||
console.log("=== 开始对原图层进行栅格化处理,移除选区内容 ===");
|
||||
|
||||
|
||||
@@ -92,9 +92,6 @@ export class EraserCommand extends Command {
|
||||
if (!this.layerManager) return;
|
||||
|
||||
const canvasObjects = this.canvas.getObjects();
|
||||
restoreObjectLayerAssociations(
|
||||
this.layerManager?.layers?.value,
|
||||
canvasObjects
|
||||
);
|
||||
restoreObjectLayerAssociations(this.layerManager?.layers?.value, canvasObjects);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { generateId } from "../utils/helper";
|
||||
import {
|
||||
createLayer,
|
||||
findLayerRecursively,
|
||||
LayerType,
|
||||
OperationType,
|
||||
} from "../utils/layerHelper";
|
||||
import { createLayer, findLayerRecursively, LayerType, OperationType } from "../utils/layerHelper";
|
||||
import { Command } from "./Command";
|
||||
import {
|
||||
findObjectById,
|
||||
@@ -33,14 +28,12 @@ export class MergeGroupLayerCommand extends Command {
|
||||
this.originalObjects = [...this.canvas.getObjects()];
|
||||
this.flattenedLayer = null;
|
||||
this.flattenedLayerId =
|
||||
generateId("flattened_") ||
|
||||
`flattened_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
generateId("flattened_") || `flattened_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
|
||||
this.existingGroupId = null; // 用于查找现有组对象
|
||||
// 组对象相关
|
||||
this.newGroupId =
|
||||
generateId("group") ||
|
||||
`group_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
generateId("group") || `group_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
@@ -172,15 +165,11 @@ export class MergeGroupLayerCommand extends Command {
|
||||
});
|
||||
|
||||
// 获取源图层在数组中的索引
|
||||
const sourceIndex = this.layers.value.findIndex(
|
||||
(l) => l.id === this.layerId
|
||||
);
|
||||
const sourceIndex = this.layers.value.findIndex((l) => l.id === this.layerId);
|
||||
|
||||
// 移除所有相关图层
|
||||
const layerIdsToRemove = layersToFlatten.map((layer) => layer.id);
|
||||
this.layers.value = this.layers.value.filter(
|
||||
(layer) => !layerIdsToRemove.includes(layer.id)
|
||||
);
|
||||
this.layers.value = this.layers.value.filter((layer) => !layerIdsToRemove.includes(layer.id));
|
||||
|
||||
// 在原位置插入展平后的图层
|
||||
this.layers.value.splice(sourceIndex, 0, this.flattenedLayer);
|
||||
@@ -202,11 +191,7 @@ export class MergeGroupLayerCommand extends Command {
|
||||
}
|
||||
|
||||
async undo() {
|
||||
if (
|
||||
!this.flattenedLayer ||
|
||||
!this.originalLayers ||
|
||||
!this.originalObjectStates
|
||||
) {
|
||||
if (!this.flattenedLayer || !this.originalLayers || !this.originalObjectStates) {
|
||||
console.warn("没有可撤销的数据");
|
||||
return;
|
||||
}
|
||||
@@ -324,10 +309,7 @@ export class MergeGroupLayerCommand extends Command {
|
||||
groupObjects.forEach((obj) => {
|
||||
try {
|
||||
// 计算对象的绝对位置
|
||||
const absolutePosition = this._calculateObjectAbsolutePosition(
|
||||
obj,
|
||||
groupObject
|
||||
);
|
||||
const absolutePosition = this._calculateObjectAbsolutePosition(obj, groupObject);
|
||||
|
||||
// 使用removeWithUpdate移除对象,这会自动恢复对象的独立状态
|
||||
groupObject.removeWithUpdate(obj);
|
||||
@@ -382,10 +364,7 @@ export class MergeGroupLayerCommand extends Command {
|
||||
const objectPoint = new fabric.Point(obj.left || 0, obj.top || 0);
|
||||
|
||||
// 应用组的变换矩阵计算绝对位置
|
||||
const absolutePoint = fabric.util.transformPoint(
|
||||
objectPoint,
|
||||
groupTransform
|
||||
);
|
||||
const absolutePoint = fabric.util.transformPoint(objectPoint, groupTransform);
|
||||
|
||||
// 计算缩放比例
|
||||
const totalScaleX = (group.scaleX || 1) * (obj.scaleX || 1);
|
||||
@@ -483,9 +462,7 @@ export class MergeGroupLayerCommand extends Command {
|
||||
canvasObj.layerId = layer.id;
|
||||
canvasObj.layerName = layer.name;
|
||||
|
||||
console.log(
|
||||
`🔗 恢复对象 ${canvasObj.id} 与图层 ${layer.name} 的关联`
|
||||
);
|
||||
console.log(`🔗 恢复对象 ${canvasObj.id} 与图层 ${layer.name} 的关联`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import {
|
||||
createLayer,
|
||||
findInChildLayers,
|
||||
LayerType,
|
||||
} from "../utils/layerHelper.js";
|
||||
import { createLayer, findInChildLayers, LayerType } from "../utils/layerHelper.js";
|
||||
import { createRasterizedImage } from "../utils/selectionToImage.js";
|
||||
import { CompositeCommand, Command } from "./Command.js";
|
||||
import {
|
||||
CreateImageLayerCommand,
|
||||
RemoveLayerCommand,
|
||||
} from "./LayerCommands.js";
|
||||
import { CreateImageLayerCommand, RemoveLayerCommand } from "./LayerCommands.js";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { generateId } from "../utils/helper.js";
|
||||
|
||||
@@ -65,9 +58,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
const selectionObject = this.selectionManager.getSelectionObject();
|
||||
if (selectionObject) {
|
||||
try {
|
||||
this._clonedSelectionObject = await this._cloneObject(
|
||||
selectionObject
|
||||
);
|
||||
this._clonedSelectionObject = await this._cloneObject(selectionObject);
|
||||
console.log("套索抠图:选区对象已克隆保存");
|
||||
} catch (error) {
|
||||
console.error("套索抠图:克隆选区对象失败:", error);
|
||||
@@ -103,14 +94,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
const sourceObjects = this._getLayerObjects(activeLayer);
|
||||
this.originalCanvasObjects = sourceObjects; // 保存真实对象引用
|
||||
this.originalFabricObjects = sourceObjects.map((obj) =>
|
||||
obj.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"parentId",
|
||||
"type",
|
||||
"custom",
|
||||
])
|
||||
obj.toObject(["id", "layerId", "layerName", "parentId", "type", "custom"])
|
||||
);
|
||||
|
||||
console.log(
|
||||
@@ -353,10 +337,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
await command.undo();
|
||||
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
|
||||
// 子命令撤销失败不中断整个撤销过程
|
||||
}
|
||||
}
|
||||
@@ -433,16 +414,11 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
// 递归获取图层及其子图层的所有对象
|
||||
const collectLayerObjects = (currentLayer) => {
|
||||
// 处理图层的fabricObjects
|
||||
if (
|
||||
currentLayer.fabricObjects &&
|
||||
Array.isArray(currentLayer.fabricObjects)
|
||||
) {
|
||||
if (currentLayer.fabricObjects && Array.isArray(currentLayer.fabricObjects)) {
|
||||
currentLayer.fabricObjects.forEach((fabricRef) => {
|
||||
if (fabricRef && fabricRef.id) {
|
||||
// 从画布中查找真实的对象
|
||||
const realObject = canvasObjects.find(
|
||||
(obj) => obj.id === fabricRef.id
|
||||
);
|
||||
const realObject = canvasObjects.find((obj) => obj.id === fabricRef.id);
|
||||
if (realObject && realObject.visible) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -452,9 +428,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 处理单个fabricObject(背景图层等)
|
||||
if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
|
||||
const realObject = canvasObjects.find(
|
||||
(obj) => obj.id === currentLayer.fabricObject.id
|
||||
);
|
||||
const realObject = canvasObjects.find((obj) => obj.id === currentLayer.fabricObject.id);
|
||||
if (realObject && realObject.visible) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -485,11 +459,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
* @returns {String} 抠图结果的DataURL
|
||||
* @private
|
||||
*/
|
||||
async _performCutoutWithRasterized(
|
||||
sourceObjects,
|
||||
selectionObject,
|
||||
selectionBounds
|
||||
) {
|
||||
async _performCutoutWithRasterized(sourceObjects, selectionObject, selectionBounds) {
|
||||
try {
|
||||
console.log("=== 开始使用createRasterizedImage执行抠图 ===");
|
||||
console.log(`源对象数量: ${sourceObjects.length}`);
|
||||
@@ -528,10 +498,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 如果createRasterizedImage失败,回退到原始方法
|
||||
console.log("⚠️ 回退到原始抠图方法...");
|
||||
return await this._performCutout(
|
||||
{ fabricObjects: sourceObjects },
|
||||
selectionObject
|
||||
);
|
||||
return await this._performCutout({ fabricObjects: sourceObjects }, selectionObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,9 +529,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
try {
|
||||
// 收集源图层中的可见对象
|
||||
const visibleObjects = sourceLayer.fabricObjects.filter(
|
||||
(obj) => obj.visible
|
||||
);
|
||||
const visibleObjects = sourceLayer.fabricObjects.filter((obj) => obj.visible);
|
||||
|
||||
if (visibleObjects.length === 0) {
|
||||
throw new Error("源图层没有可见对象");
|
||||
@@ -617,10 +582,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
let highResolutionScale = 1;
|
||||
if (this.highResolutionEnabled) {
|
||||
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
highResolutionScale = Math.max(
|
||||
this.baseResolutionScale,
|
||||
devicePixelRatio * 2
|
||||
);
|
||||
highResolutionScale = Math.max(this.baseResolutionScale, devicePixelRatio * 2);
|
||||
}
|
||||
|
||||
// 创建用于导出的高分辨率canvas
|
||||
@@ -631,12 +593,8 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
colorSpace: "srgb",
|
||||
});
|
||||
|
||||
const actualWidth = Math.round(
|
||||
renderBounds.width * highResolutionScale
|
||||
);
|
||||
const actualHeight = Math.round(
|
||||
renderBounds.height * highResolutionScale
|
||||
);
|
||||
const actualWidth = Math.round(renderBounds.width * highResolutionScale);
|
||||
const actualHeight = Math.round(renderBounds.height * highResolutionScale);
|
||||
|
||||
exportCanvas.width = actualWidth;
|
||||
exportCanvas.height = actualHeight;
|
||||
@@ -845,17 +803,14 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "polygon") {
|
||||
fabric.Polygon.fromObject(
|
||||
this.serializedSelectionObject,
|
||||
(polygon) => {
|
||||
if (polygon) {
|
||||
console.log("多边形选区对象反序列化成功");
|
||||
resolve(polygon);
|
||||
} else {
|
||||
reject(new Error("多边形选区对象反序列化失败"));
|
||||
}
|
||||
fabric.Polygon.fromObject(this.serializedSelectionObject, (polygon) => {
|
||||
if (polygon) {
|
||||
console.log("多边形选区对象反序列化成功");
|
||||
resolve(polygon);
|
||||
} else {
|
||||
reject(new Error("多边形选区对象反序列化失败"));
|
||||
}
|
||||
);
|
||||
});
|
||||
} else if (objectType === "rect") {
|
||||
fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => {
|
||||
if (rect) {
|
||||
@@ -866,30 +821,24 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "ellipse" || objectType === "circle") {
|
||||
fabric.Ellipse.fromObject(
|
||||
this.serializedSelectionObject,
|
||||
(ellipse) => {
|
||||
if (ellipse) {
|
||||
console.log("椭圆选区对象反序列化成功");
|
||||
resolve(ellipse);
|
||||
} else {
|
||||
reject(new Error("椭圆选区对象反序列化失败"));
|
||||
}
|
||||
fabric.Ellipse.fromObject(this.serializedSelectionObject, (ellipse) => {
|
||||
if (ellipse) {
|
||||
console.log("椭圆选区对象反序列化成功");
|
||||
resolve(ellipse);
|
||||
} else {
|
||||
reject(new Error("椭圆选区对象反序列化失败"));
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// 通用对象反序列化
|
||||
fabric.util.enlivenObjects(
|
||||
[this.serializedSelectionObject],
|
||||
(objects) => {
|
||||
if (objects && objects.length > 0) {
|
||||
console.log("通用选区对象反序列化成功");
|
||||
resolve(objects[0]);
|
||||
} else {
|
||||
reject(new Error("通用选区对象反序列化失败"));
|
||||
}
|
||||
fabric.util.enlivenObjects([this.serializedSelectionObject], (objects) => {
|
||||
if (objects && objects.length > 0) {
|
||||
console.log("通用选区对象反序列化成功");
|
||||
resolve(objects[0]);
|
||||
} else {
|
||||
reject(new Error("通用选区对象反序列化失败"));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -913,11 +862,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 1. 恢复图层到原位置
|
||||
if (this.originalLayerIndex !== -1) {
|
||||
this.layerManager.layers.value.splice(
|
||||
this.originalLayerIndex,
|
||||
0,
|
||||
this.originalLayer
|
||||
);
|
||||
this.layerManager.layers.value.splice(this.originalLayerIndex, 0, this.originalLayer);
|
||||
} else {
|
||||
// 如果没有保存索引,添加到末尾
|
||||
this.layerManager.layers.value.push(this.originalLayer);
|
||||
@@ -925,9 +870,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 2. 恢复fabric对象到画布
|
||||
if (this.originalFabricObjects.length > 0) {
|
||||
console.log(
|
||||
`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`
|
||||
);
|
||||
console.log(`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`);
|
||||
|
||||
// 使用fabric.util.enlivenObjects批量反序列化对象
|
||||
await new Promise((resolve, reject) => {
|
||||
@@ -1076,12 +1019,7 @@ export class ClearSelectionCommand extends Command {
|
||||
// 序列化选区对象和相关状态
|
||||
this.originalSelectionState = {
|
||||
// 选区对象的序列化数据
|
||||
selectionObjectData: selectionObject.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"parentId",
|
||||
]),
|
||||
selectionObjectData: selectionObject.toObject(["id", "layerId", "layerName", "parentId"]),
|
||||
// 选区路径数据
|
||||
selectionPath: this.selectionManager.getSelectionPath(),
|
||||
// 羽化值
|
||||
@@ -1144,13 +1082,8 @@ export class ClearSelectionCommand extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
selectionObjectData,
|
||||
featherAmount,
|
||||
selectionStyle,
|
||||
position,
|
||||
managerState,
|
||||
} = this.originalSelectionState;
|
||||
const { selectionObjectData, featherAmount, selectionStyle, position, managerState } =
|
||||
this.originalSelectionState;
|
||||
|
||||
// 根据选区对象类型进行反序列化
|
||||
const objectType = selectionObjectData.type;
|
||||
@@ -1196,9 +1129,7 @@ export class ClearSelectionCommand extends Command {
|
||||
|
||||
// 恢复阴影(羽化效果)
|
||||
if (selectionStyle.shadow) {
|
||||
restoredObject.shadow = new fabric.Shadow(
|
||||
selectionStyle.shadow
|
||||
);
|
||||
restoredObject.shadow = new fabric.Shadow(selectionStyle.shadow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1283,9 +1214,7 @@ export class ClearSelectionCommand extends Command {
|
||||
// 验证基本属性
|
||||
const originalId = this.originalSelectionState.selectionId;
|
||||
if (currentSelection.id !== originalId) {
|
||||
console.warn(
|
||||
`选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}`
|
||||
);
|
||||
console.warn(`选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1293,9 +1222,7 @@ export class ClearSelectionCommand extends Command {
|
||||
const currentFeather = this.selectionManager.getFeatherAmount();
|
||||
const originalFeather = this.originalSelectionState.featherAmount;
|
||||
if (currentFeather !== originalFeather) {
|
||||
console.warn(
|
||||
`羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}`
|
||||
);
|
||||
console.warn(`羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import {
|
||||
createLayer,
|
||||
findInChildLayers,
|
||||
LayerType,
|
||||
OperationType,
|
||||
} from "../utils/layerHelper.js";
|
||||
import { createLayer, findInChildLayers, LayerType, OperationType } from "../utils/layerHelper.js";
|
||||
import { createRasterizedImage } from "../utils/selectionToImage.js";
|
||||
import { CompositeCommand, Command } from "./Command.js";
|
||||
import {
|
||||
CreateImageLayerCommand,
|
||||
RemoveLayerCommand,
|
||||
} from "./LayerCommands.js";
|
||||
import { CreateImageLayerCommand, RemoveLayerCommand } from "./LayerCommands.js";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { generateId } from "../utils/helper.js";
|
||||
import { ToolCommand } from "./ToolCommands.js";
|
||||
@@ -69,9 +61,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
const selectionObject = this.selectionManager.getSelectionObject();
|
||||
if (selectionObject) {
|
||||
try {
|
||||
this._clonedSelectionObject = await this._cloneObject(
|
||||
selectionObject
|
||||
);
|
||||
this._clonedSelectionObject = await this._cloneObject(selectionObject);
|
||||
console.log("套索抠图:选区对象已克隆保存");
|
||||
} catch (error) {
|
||||
console.error("套索抠图:克隆选区对象失败:", error);
|
||||
@@ -107,14 +97,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
const sourceObjects = this._getLayerObjects(activeLayer);
|
||||
this.originalCanvasObjects = sourceObjects; // 保存真实对象引用
|
||||
this.originalFabricObjects = sourceObjects.map((obj) =>
|
||||
obj.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"parentId",
|
||||
"type",
|
||||
"custom",
|
||||
])
|
||||
obj.toObject(["id", "layerId", "layerName", "parentId", "type", "custom"])
|
||||
);
|
||||
|
||||
console.log(
|
||||
@@ -385,10 +368,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
await command.undo();
|
||||
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
|
||||
// 子命令撤销失败不中断整个撤销过程
|
||||
}
|
||||
}
|
||||
@@ -465,16 +445,11 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
// 递归获取图层及其子图层的所有对象
|
||||
const collectLayerObjects = (currentLayer) => {
|
||||
// 处理图层的fabricObjects
|
||||
if (
|
||||
currentLayer.fabricObjects &&
|
||||
Array.isArray(currentLayer.fabricObjects)
|
||||
) {
|
||||
if (currentLayer.fabricObjects && Array.isArray(currentLayer.fabricObjects)) {
|
||||
currentLayer.fabricObjects.forEach((fabricRef) => {
|
||||
if (fabricRef && fabricRef.id) {
|
||||
// 从画布中查找真实的对象
|
||||
const realObject = canvasObjects.find(
|
||||
(obj) => obj.id === fabricRef.id
|
||||
);
|
||||
const realObject = canvasObjects.find((obj) => obj.id === fabricRef.id);
|
||||
if (realObject && realObject.visible) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -484,9 +459,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 处理单个fabricObject(背景图层等)
|
||||
if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
|
||||
const realObject = canvasObjects.find(
|
||||
(obj) => obj.id === currentLayer.fabricObject.id
|
||||
);
|
||||
const realObject = canvasObjects.find((obj) => obj.id === currentLayer.fabricObject.id);
|
||||
if (realObject && realObject.visible) {
|
||||
objects.push(realObject);
|
||||
}
|
||||
@@ -517,11 +490,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
* @returns {String} 抠图结果的DataURL
|
||||
* @private
|
||||
*/
|
||||
async _performCutoutWithRasterized(
|
||||
sourceObjects,
|
||||
selectionObject,
|
||||
selectionBounds
|
||||
) {
|
||||
async _performCutoutWithRasterized(sourceObjects, selectionObject, selectionBounds) {
|
||||
try {
|
||||
console.log("=== 开始使用createRasterizedImage执行抠图 ===");
|
||||
console.log(`源对象数量: ${sourceObjects.length}`);
|
||||
@@ -560,10 +529,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 如果createRasterizedImage失败,回退到原始方法
|
||||
console.log("⚠️ 回退到原始抠图方法...");
|
||||
return await this._performCutout(
|
||||
{ fabricObjects: sourceObjects },
|
||||
selectionObject
|
||||
);
|
||||
return await this._performCutout({ fabricObjects: sourceObjects }, selectionObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,9 +560,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
try {
|
||||
// 收集源图层中的可见对象
|
||||
const visibleObjects = sourceLayer.fabricObjects.filter(
|
||||
(obj) => obj.visible
|
||||
);
|
||||
const visibleObjects = sourceLayer.fabricObjects.filter((obj) => obj.visible);
|
||||
|
||||
if (visibleObjects.length === 0) {
|
||||
throw new Error("源图层没有可见对象");
|
||||
@@ -649,10 +613,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
let highResolutionScale = 1;
|
||||
if (this.highResolutionEnabled) {
|
||||
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
highResolutionScale = Math.max(
|
||||
this.baseResolutionScale,
|
||||
devicePixelRatio * 2
|
||||
);
|
||||
highResolutionScale = Math.max(this.baseResolutionScale, devicePixelRatio * 2);
|
||||
}
|
||||
|
||||
// 创建用于导出的高分辨率canvas
|
||||
@@ -663,12 +624,8 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
colorSpace: "srgb",
|
||||
});
|
||||
|
||||
const actualWidth = Math.round(
|
||||
renderBounds.width * highResolutionScale
|
||||
);
|
||||
const actualHeight = Math.round(
|
||||
renderBounds.height * highResolutionScale
|
||||
);
|
||||
const actualWidth = Math.round(renderBounds.width * highResolutionScale);
|
||||
const actualHeight = Math.round(renderBounds.height * highResolutionScale);
|
||||
|
||||
exportCanvas.width = actualWidth;
|
||||
exportCanvas.height = actualHeight;
|
||||
@@ -877,17 +834,14 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "polygon") {
|
||||
fabric.Polygon.fromObject(
|
||||
this.serializedSelectionObject,
|
||||
(polygon) => {
|
||||
if (polygon) {
|
||||
console.log("多边形选区对象反序列化成功");
|
||||
resolve(polygon);
|
||||
} else {
|
||||
reject(new Error("多边形选区对象反序列化失败"));
|
||||
}
|
||||
fabric.Polygon.fromObject(this.serializedSelectionObject, (polygon) => {
|
||||
if (polygon) {
|
||||
console.log("多边形选区对象反序列化成功");
|
||||
resolve(polygon);
|
||||
} else {
|
||||
reject(new Error("多边形选区对象反序列化失败"));
|
||||
}
|
||||
);
|
||||
});
|
||||
} else if (objectType === "rect") {
|
||||
fabric.Rect.fromObject(this.serializedSelectionObject, (rect) => {
|
||||
if (rect) {
|
||||
@@ -898,30 +852,24 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
}
|
||||
});
|
||||
} else if (objectType === "ellipse" || objectType === "circle") {
|
||||
fabric.Ellipse.fromObject(
|
||||
this.serializedSelectionObject,
|
||||
(ellipse) => {
|
||||
if (ellipse) {
|
||||
console.log("椭圆选区对象反序列化成功");
|
||||
resolve(ellipse);
|
||||
} else {
|
||||
reject(new Error("椭圆选区对象反序列化失败"));
|
||||
}
|
||||
fabric.Ellipse.fromObject(this.serializedSelectionObject, (ellipse) => {
|
||||
if (ellipse) {
|
||||
console.log("椭圆选区对象反序列化成功");
|
||||
resolve(ellipse);
|
||||
} else {
|
||||
reject(new Error("椭圆选区对象反序列化失败"));
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// 通用对象反序列化
|
||||
fabric.util.enlivenObjects(
|
||||
[this.serializedSelectionObject],
|
||||
(objects) => {
|
||||
if (objects && objects.length > 0) {
|
||||
console.log("通用选区对象反序列化成功");
|
||||
resolve(objects[0]);
|
||||
} else {
|
||||
reject(new Error("通用选区对象反序列化失败"));
|
||||
}
|
||||
fabric.util.enlivenObjects([this.serializedSelectionObject], (objects) => {
|
||||
if (objects && objects.length > 0) {
|
||||
console.log("通用选区对象反序列化成功");
|
||||
resolve(objects[0]);
|
||||
} else {
|
||||
reject(new Error("通用选区对象反序列化失败"));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -945,11 +893,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 1. 恢复图层到原位置
|
||||
if (this.originalLayerIndex !== -1) {
|
||||
this.layerManager.layers.value.splice(
|
||||
this.originalLayerIndex,
|
||||
0,
|
||||
this.originalLayer
|
||||
);
|
||||
this.layerManager.layers.value.splice(this.originalLayerIndex, 0, this.originalLayer);
|
||||
} else {
|
||||
// 如果没有保存索引,添加到末尾
|
||||
this.layerManager.layers.value.push(this.originalLayer);
|
||||
@@ -957,9 +901,7 @@ export class LassoCutoutCommand extends CompositeCommand {
|
||||
|
||||
// 2. 恢复fabric对象到画布
|
||||
if (this.originalFabricObjects.length > 0) {
|
||||
console.log(
|
||||
`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`
|
||||
);
|
||||
console.log(`↩️ 恢复 ${this.originalFabricObjects.length} 个fabric对象`);
|
||||
|
||||
// 使用fabric.util.enlivenObjects批量反序列化对象
|
||||
await new Promise((resolve, reject) => {
|
||||
@@ -1108,12 +1050,7 @@ export class ClearSelectionCommand extends Command {
|
||||
// 序列化选区对象和相关状态
|
||||
this.originalSelectionState = {
|
||||
// 选区对象的序列化数据
|
||||
selectionObjectData: selectionObject.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"parentId",
|
||||
]),
|
||||
selectionObjectData: selectionObject.toObject(["id", "layerId", "layerName", "parentId"]),
|
||||
// 选区路径数据
|
||||
selectionPath: this.selectionManager.getSelectionPath(),
|
||||
// 羽化值
|
||||
@@ -1176,13 +1113,8 @@ export class ClearSelectionCommand extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
selectionObjectData,
|
||||
featherAmount,
|
||||
selectionStyle,
|
||||
position,
|
||||
managerState,
|
||||
} = this.originalSelectionState;
|
||||
const { selectionObjectData, featherAmount, selectionStyle, position, managerState } =
|
||||
this.originalSelectionState;
|
||||
|
||||
// 根据选区对象类型进行反序列化
|
||||
const objectType = selectionObjectData.type;
|
||||
@@ -1228,9 +1160,7 @@ export class ClearSelectionCommand extends Command {
|
||||
|
||||
// 恢复阴影(羽化效果)
|
||||
if (selectionStyle.shadow) {
|
||||
restoredObject.shadow = new fabric.Shadow(
|
||||
selectionStyle.shadow
|
||||
);
|
||||
restoredObject.shadow = new fabric.Shadow(selectionStyle.shadow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1315,9 +1245,7 @@ export class ClearSelectionCommand extends Command {
|
||||
// 验证基本属性
|
||||
const originalId = this.originalSelectionState.selectionId;
|
||||
if (currentSelection.id !== originalId) {
|
||||
console.warn(
|
||||
`选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}`
|
||||
);
|
||||
console.warn(`选区ID不匹配: 期望 ${originalId}, 实际 ${currentSelection.id}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1325,9 +1253,7 @@ export class ClearSelectionCommand extends Command {
|
||||
const currentFeather = this.selectionManager.getFeatherAmount();
|
||||
const originalFeather = this.originalSelectionState.featherAmount;
|
||||
if (currentFeather !== originalFeather) {
|
||||
console.warn(
|
||||
`羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}`
|
||||
);
|
||||
console.warn(`羽化值不匹配: 期望 ${originalFeather}, 实际 ${currentFeather}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { Command } from "./Command";
|
||||
import {
|
||||
createLayer,
|
||||
findLayerRecursively,
|
||||
LayerType,
|
||||
OperationType,
|
||||
} from "../utils/layerHelper";
|
||||
import { createLayer, findLayerRecursively, LayerType, OperationType } from "../utils/layerHelper";
|
||||
import { createStaticCanvas } from "../utils/canvasFactory";
|
||||
import { AddObjectToLayerCommand } from "./ObjectLayerCommands";
|
||||
import { ToolCommand } from "./ToolCommands";
|
||||
import {
|
||||
findObjectById,
|
||||
generateId,
|
||||
getObjectZIndex,
|
||||
insertObjectAtZIndex,
|
||||
objectIsInCanvas,
|
||||
optimizeCanvasRendering,
|
||||
removeCanvasObjectByObject,
|
||||
@@ -112,15 +109,11 @@ export class AddLayerCommand extends Command {
|
||||
parentLayer.children = parentLayer.children || [];
|
||||
parentLayer.children.splice(insertIndex, 0, newLayer);
|
||||
|
||||
console.log(
|
||||
`新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`
|
||||
);
|
||||
console.log(`新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`);
|
||||
} else {
|
||||
// 当前激活图层是一级图层
|
||||
// 在一级图层中,插入到激活图层之上
|
||||
const activeLayerIndex = layers.findIndex(
|
||||
(layer) => layer.id === currentActiveLayerId
|
||||
);
|
||||
const activeLayerIndex = layers.findIndex((layer) => layer.id === currentActiveLayerId);
|
||||
insertIndex = Math.max(0, activeLayerIndex);
|
||||
layers.splice(insertIndex, 0, newLayer);
|
||||
|
||||
@@ -273,9 +266,7 @@ export class PasteLayerCommand extends Command {
|
||||
try {
|
||||
objects.forEach((obj) => {
|
||||
// 生成新的对象ID
|
||||
const newObjId = `obj_${Date.now()}_${Math.floor(
|
||||
Math.random() * 1000
|
||||
)}`;
|
||||
const newObjId = `obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
obj.id = newObjId;
|
||||
obj.layerId = this.newLayerId;
|
||||
obj.layerName = this.newLayer.name;
|
||||
@@ -337,9 +328,7 @@ export class PasteLayerCommand extends Command {
|
||||
if (!this.newLayer || !this.newLayerId) return;
|
||||
|
||||
// 从图层列表删除该图层
|
||||
const index = this.layers.value.findIndex(
|
||||
(layer) => layer.id === this.newLayerId
|
||||
);
|
||||
const index = this.layers.value.findIndex((layer) => layer.id === this.newLayerId);
|
||||
if (index !== -1) {
|
||||
this.layers.value.splice(index, 1);
|
||||
}
|
||||
@@ -405,9 +394,7 @@ export class RemoveLayerCommand extends Command {
|
||||
this.activeLayerId = options.activeLayerId;
|
||||
|
||||
// 查找要删除的图层
|
||||
this.layerIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.id === this.layerId
|
||||
);
|
||||
this.layerIndex = this.layers.value.findIndex((layer) => layer.id === this.layerId);
|
||||
this.removedLayer = this.layers.value[this.layerIndex];
|
||||
this.isActiveLayer = this.layerId === this.activeLayerId.value;
|
||||
|
||||
@@ -474,9 +461,7 @@ export class RemoveLayerCommand extends Command {
|
||||
// 如果删除的是当前活动图层,需要更新活动图层
|
||||
if (this.isActiveLayer) {
|
||||
// 查找最近的非背景层作为新的活动图层
|
||||
const newActiveLayer = this.layers.value.find(
|
||||
(layer) => !layer.isBackground
|
||||
);
|
||||
const newActiveLayer = this.layers.value.find((layer) => !layer.isBackground);
|
||||
if (newActiveLayer) {
|
||||
this.activeLayerId.value = newActiveLayer.id;
|
||||
} else {
|
||||
@@ -554,30 +539,22 @@ export class MoveLayerCommand extends Command {
|
||||
|
||||
this.parentLayer = null; // 父图层
|
||||
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
|
||||
|
||||
// 如果parent 有值 或者 layer 上有parentId 则视为子图层的置顶
|
||||
if (parent?.id) {
|
||||
// 查找子图层索引
|
||||
this.layerIndex = parent?.children?.findIndex(
|
||||
(layer) => layer.id === this.layerId
|
||||
);
|
||||
this.layerIndex = parent?.children?.findIndex((layer) => layer.id === this.layerId);
|
||||
this.parentLayer = parent;
|
||||
} else {
|
||||
// 查找图层索引
|
||||
this.layerIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.id === this.layerId
|
||||
);
|
||||
this.layerIndex = this.layers.value.findIndex((layer) => layer.id === this.layerId);
|
||||
}
|
||||
|
||||
this.layer = layer;
|
||||
this.originalIndex = this.layerIndex;
|
||||
// 目标位置
|
||||
this.targetIndex =
|
||||
options.direction === "up" ? this.layerIndex - 1 : this.layerIndex + 1;
|
||||
this.targetIndex = options.direction === "up" ? this.layerIndex - 1 : this.layerIndex + 1;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
@@ -668,9 +645,7 @@ export class ToggleLayerVisibilityCommand extends Command {
|
||||
|
||||
// 更新画布上图层对象的可见性
|
||||
if (this.canvas) {
|
||||
const layerObjects = this.canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.layerId === this.layerId);
|
||||
const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
|
||||
layerObjects.forEach((obj) => {
|
||||
obj.visible = this.layer.visible;
|
||||
});
|
||||
@@ -711,10 +686,7 @@ export class ToggleChildLayerVisibilityCommand extends Command {
|
||||
this.layerManager = options.layerManager;
|
||||
|
||||
// 查找父图层和子图层
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
|
||||
this.parentLayer = parent;
|
||||
this.childLayer = layer;
|
||||
|
||||
@@ -731,9 +703,7 @@ export class ToggleChildLayerVisibilityCommand extends Command {
|
||||
|
||||
// 更新画布上图层对象的可见性
|
||||
if (this.canvas) {
|
||||
const layerObjects = this.canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.layerId === this.layerId);
|
||||
const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
|
||||
|
||||
layerObjects.forEach((obj) => {
|
||||
obj.visible = this.childLayer.visible;
|
||||
@@ -792,9 +762,7 @@ export class RenameLayerCommand extends Command {
|
||||
|
||||
// 更新图层对象上的图层名称
|
||||
if (this.canvas) {
|
||||
const layerObjects = this.canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.layerId === this.layerId);
|
||||
const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
|
||||
|
||||
layerObjects.forEach((obj) => {
|
||||
obj.layerName = this.newName;
|
||||
@@ -811,9 +779,7 @@ export class RenameLayerCommand extends Command {
|
||||
|
||||
// 恢复图层对象上的图层名称
|
||||
if (this.canvas) {
|
||||
const layerObjects = this.canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.layerId === this.layerId);
|
||||
const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
|
||||
|
||||
layerObjects.forEach((obj) => {
|
||||
obj.layerName = this.oldName;
|
||||
@@ -848,10 +814,7 @@ export class LayerLockCommand extends Command {
|
||||
|
||||
// 查找图层(包括子图层)
|
||||
// 查找图层
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
|
||||
this.layer = layer;
|
||||
this.parentLayer = parent || null; // 父图层
|
||||
this.oldLocked = this.layer ? this.layer.locked : null;
|
||||
@@ -869,11 +832,7 @@ export class LayerLockCommand extends Command {
|
||||
layer.locked = locked;
|
||||
|
||||
// 如果是组图层,递归更新所有子图层
|
||||
if (
|
||||
layer.type === "group" &&
|
||||
layer.children &&
|
||||
Array.isArray(layer.children)
|
||||
) {
|
||||
if (layer.type === "group" && layer.children && Array.isArray(layer.children)) {
|
||||
layer.children.forEach((child) => {
|
||||
this._updateLayerLockState(child, locked);
|
||||
});
|
||||
@@ -895,11 +854,7 @@ export class LayerLockCommand extends Command {
|
||||
// 更新画布上对象的可选择状态
|
||||
await this.layerManager?.updateLayersObjectsInteractivity();
|
||||
|
||||
console.log(
|
||||
`${newLocked ? "锁定" : "解锁"}图层: ${this.layer.name} (ID: ${
|
||||
this.layerId
|
||||
})`
|
||||
);
|
||||
console.log(`${newLocked ? "锁定" : "解锁"}图层: ${this.layer.name} (ID: ${this.layerId})`);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -911,9 +866,7 @@ export class LayerLockCommand extends Command {
|
||||
// 更新画布上对象的可选择状态
|
||||
await this.layerManager?.updateLayersObjectsInteractivity();
|
||||
|
||||
console.log(
|
||||
`恢复图层锁定状态: ${this.layer.name} (锁定: ${this.oldLocked})`
|
||||
);
|
||||
console.log(`恢复图层锁定状态: ${this.layer.name} (锁定: ${this.oldLocked})`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -961,9 +914,7 @@ export class SetLayerOpacityCommand extends Command {
|
||||
|
||||
// 更新画布上对象的不透明度
|
||||
if (this.canvas) {
|
||||
const layerObjects = this.canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.layerId === this.layerId);
|
||||
const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
|
||||
|
||||
layerObjects.forEach((obj) => {
|
||||
obj.opacity = this.opacity;
|
||||
@@ -982,9 +933,7 @@ export class SetLayerOpacityCommand extends Command {
|
||||
|
||||
// 更新画布上对象的不透明度
|
||||
if (this.canvas) {
|
||||
const layerObjects = this.canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.layerId === this.layerId);
|
||||
const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
|
||||
|
||||
layerObjects.forEach((obj) => {
|
||||
obj.opacity = this.oldOpacity;
|
||||
@@ -1038,9 +987,7 @@ export class SetLayerBlendModeCommand extends Command {
|
||||
|
||||
// 更新画布上对象的混合模式
|
||||
if (this.canvas) {
|
||||
const layerObjects = this.canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.layerId === this.layerId);
|
||||
const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
|
||||
|
||||
layerObjects.forEach((obj) => {
|
||||
obj.globalCompositeOperation = this.blendMode;
|
||||
@@ -1059,9 +1006,7 @@ export class SetLayerBlendModeCommand extends Command {
|
||||
|
||||
// 更新画布上对象的混合模式
|
||||
if (this.canvas) {
|
||||
const layerObjects = this.canvas
|
||||
.getObjects()
|
||||
.filter((obj) => obj.layerId === this.layerId);
|
||||
const layerObjects = this.canvas.getObjects().filter((obj) => obj.layerId === this.layerId);
|
||||
|
||||
layerObjects.forEach((obj) => {
|
||||
obj.globalCompositeOperation = this.oldBlendMode;
|
||||
@@ -1101,17 +1046,11 @@ export class MergeLayersCommand extends Command {
|
||||
// 备份原图层
|
||||
this.originalLayers = [...this.layers.value];
|
||||
// 新图层ID
|
||||
this.newLayerId = `merged_layer_${Date.now()}_${Math.floor(
|
||||
Math.random() * 1000
|
||||
)}`;
|
||||
this.newLayerId = `merged_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
}
|
||||
|
||||
execute() {
|
||||
if (
|
||||
!this.layerIds ||
|
||||
!Array.isArray(this.layerIds) ||
|
||||
this.layerIds.length < 2
|
||||
) {
|
||||
if (!this.layerIds || !Array.isArray(this.layerIds) || this.layerIds.length < 2) {
|
||||
console.error("合并图层至少需要两个图层");
|
||||
return null;
|
||||
}
|
||||
@@ -1132,6 +1071,14 @@ export class MergeLayersCommand extends Command {
|
||||
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(
|
||||
...layersToGroup.map((layer) =>
|
||||
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(
|
||||
(layer) => !this.layerIds.includes(layer.id)
|
||||
);
|
||||
this.layers.value = this.layers.value.filter((layer) => !this.layerIds.includes(layer.id));
|
||||
|
||||
// 插入新图层
|
||||
this.layers.value.splice(topLayerIndex, 0, mergedLayer);
|
||||
@@ -1227,18 +1172,13 @@ export class GroupLayersCommand extends Command {
|
||||
this.originalLayers = [...this.layers.value];
|
||||
// 新组ID
|
||||
this.groupId =
|
||||
generateId("group_layer_") ||
|
||||
`group_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
generateId("group_layer_") || `group_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
|
||||
this.originalActiveLayerId = this.activeLayerId.value; // 备份原活动图层ID
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (
|
||||
!this.layerIds ||
|
||||
!Array.isArray(this.layerIds) ||
|
||||
this.layerIds.length < 2
|
||||
) {
|
||||
if (!this.layerIds || !Array.isArray(this.layerIds) || this.layerIds.length < 2) {
|
||||
console.error("组合图层至少需要两个图层");
|
||||
return null;
|
||||
}
|
||||
@@ -1282,9 +1222,7 @@ export class GroupLayersCommand extends Command {
|
||||
});
|
||||
|
||||
// 移除原图层
|
||||
this.layers.value = this.layers.value.filter(
|
||||
(layer) => !this.layerIds.includes(layer.id)
|
||||
);
|
||||
this.layers.value = this.layers.value.filter((layer) => !this.layerIds.includes(layer.id));
|
||||
|
||||
// 插入新组图层
|
||||
this.layers.value.splice(topLayerIndex, 0, groupLayer);
|
||||
@@ -1338,9 +1276,7 @@ export class UngroupLayersCommand extends Command {
|
||||
|
||||
async execute() {
|
||||
// 查找组图层
|
||||
const groupIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.id === this.groupId
|
||||
);
|
||||
const groupIndex = this.layers.value.findIndex((layer) => layer.id === this.groupId);
|
||||
|
||||
if (groupIndex === -1) {
|
||||
console.error(`找不到组图层 ${this.groupId}`);
|
||||
@@ -1361,11 +1297,11 @@ export class UngroupLayersCommand extends Command {
|
||||
this.layers.value.splice(
|
||||
groupIndex,
|
||||
1,
|
||||
...groupLayer.children?.map((child) => {
|
||||
...(groupLayer?.children?.map((child) => {
|
||||
// 为子图层添加parentId
|
||||
delete child.parentId; // 删除parentId属性
|
||||
return { ...child };
|
||||
})
|
||||
}) || {})
|
||||
);
|
||||
// 更新当前活动图层为第一个子图层
|
||||
if (this.childLayerIds.length > 0 && this.activeLayerId) {
|
||||
@@ -1411,18 +1347,14 @@ export class MergeLayerObjectsCommand extends Command {
|
||||
// 备份原始对象,用于撤销
|
||||
if (this.activeLayer && Array.isArray(this.activeLayer.fabricObjects)) {
|
||||
this.originalObjects =
|
||||
this.canvas
|
||||
?.getObjects()
|
||||
?.filter((fItem) => fItem.layerId === this.activeLayer.id) || [];
|
||||
this.canvas?.getObjects()?.filter((fItem) => fItem.layerId === this.activeLayer.id) || [];
|
||||
} else {
|
||||
this.originalObjects = [];
|
||||
}
|
||||
|
||||
// 新合并图像对象
|
||||
this.mergedImage = null;
|
||||
this.newImageId = `merged_image_${Date.now()}_${Math.floor(
|
||||
Math.random() * 1000
|
||||
)}`;
|
||||
this.newImageId = `merged_image_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
@@ -1458,10 +1390,7 @@ export class MergeLayerObjectsCommand extends Command {
|
||||
|
||||
// 异步处理图像合并
|
||||
try {
|
||||
const mergedImage = await this._createMergedImageAsync(
|
||||
objectsToMerge,
|
||||
bounds
|
||||
);
|
||||
const mergedImage = await this._createMergedImageAsync(objectsToMerge, bounds);
|
||||
this._setupMergedImage(mergedImage, bounds);
|
||||
this._replaceObjects(mergedImage);
|
||||
|
||||
@@ -1611,9 +1540,7 @@ export class MergeLayerObjectsCommand extends Command {
|
||||
});
|
||||
|
||||
tempCanvas.add(clonedObj);
|
||||
console.log(
|
||||
`添加对象 ${index + 1}/${objects.length}: ${obj.type || "unknown"}`
|
||||
);
|
||||
console.log(`添加对象 ${index + 1}/${objects.length}: ${obj.type || "unknown"}`);
|
||||
} catch (error) {
|
||||
console.error(`添加对象到临时画布时发生错误:`, error);
|
||||
}
|
||||
@@ -1672,10 +1599,7 @@ export class MergeLayerObjectsCommand extends Command {
|
||||
});
|
||||
|
||||
// 如果有新的图像对象,也要移除
|
||||
if (
|
||||
this.fabricImage &&
|
||||
this.canvas.getObjects().includes(this.fabricImage)
|
||||
) {
|
||||
if (this.fabricImage && this.canvas.getObjects().includes(this.fabricImage)) {
|
||||
this.canvas.remove(this.fabricImage);
|
||||
}
|
||||
|
||||
@@ -1691,9 +1615,7 @@ export class MergeLayerObjectsCommand extends Command {
|
||||
// 更新缩略图
|
||||
if (this.canvas.thumbnailManager) {
|
||||
setTimeout(() => {
|
||||
this.canvas.thumbnailManager.generateLayerThumbnail(
|
||||
this.activeLayer.id
|
||||
);
|
||||
this.canvas.thumbnailManager.generateLayerThumbnail(this.activeLayer.id);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
@@ -1737,9 +1659,7 @@ export class MergeLayerObjectsCommand extends Command {
|
||||
// 更新缩略图
|
||||
if (this.canvas.thumbnailManager) {
|
||||
setTimeout(() => {
|
||||
this.canvas.thumbnailManager.generateLayerThumbnail(
|
||||
this.activeLayer.id
|
||||
);
|
||||
this.canvas.thumbnailManager.generateLayerThumbnail(this.activeLayer.id);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
@@ -1827,8 +1747,7 @@ export class LayerObjectsToGroupCommand extends Command {
|
||||
this.existingGroupId = null;
|
||||
this.groupObjectId = null;
|
||||
this.newGroupId =
|
||||
generateId("group") ||
|
||||
`group_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
generateId("group") || `group_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
this.wasGroupCreated = false;
|
||||
|
||||
// 保存原始位置信息
|
||||
@@ -1889,10 +1808,7 @@ export class LayerObjectsToGroupCommand extends Command {
|
||||
const newObjectsToAdd = [];
|
||||
if (this.fabricImage) {
|
||||
// 如果是重做,恢复新对象的原始位置
|
||||
if (
|
||||
!this.isFirstExecution &&
|
||||
this.newObjectPositions.has(this.fabricImage.id)
|
||||
) {
|
||||
if (!this.isFirstExecution && this.newObjectPositions.has(this.fabricImage.id)) {
|
||||
const savedPosition = this.newObjectPositions.get(this.fabricImage.id);
|
||||
this.fabricImage.set({
|
||||
left: savedPosition.left,
|
||||
@@ -1905,9 +1821,7 @@ export class LayerObjectsToGroupCommand extends Command {
|
||||
flipX: savedPosition.flipX,
|
||||
flipY: savedPosition.flipY,
|
||||
});
|
||||
console.log(
|
||||
`🔄 重做时恢复新对象位置: (${savedPosition.left}, ${savedPosition.top})`
|
||||
);
|
||||
console.log(`🔄 重做时恢复新对象位置: (${savedPosition.left}, ${savedPosition.top})`);
|
||||
}
|
||||
|
||||
newObjectsToAdd.push(this.fabricImage);
|
||||
@@ -1960,9 +1874,7 @@ export class LayerObjectsToGroupCommand extends Command {
|
||||
|
||||
if (!this.existingGroupId) {
|
||||
const groupObjects =
|
||||
this.activeLayer.fabricObjects.find(
|
||||
(obj) => obj && obj.type === "group"
|
||||
) || null;
|
||||
this.activeLayer.fabricObjects.find((obj) => obj && obj.type === "group") || null;
|
||||
|
||||
this.existingGroupId = groupObjects?.id || null;
|
||||
}
|
||||
@@ -2062,9 +1974,7 @@ export class LayerObjectsToGroupCommand extends Command {
|
||||
|
||||
// 更新图层的对象列表
|
||||
// this.activeLayer.fabricObjects = [groupObject];
|
||||
this.activeLayer.fabricObjects = [
|
||||
groupObject.toObject(["id", "layerId", "layerName"]),
|
||||
];
|
||||
this.activeLayer.fabricObjects = [groupObject.toObject(["id", "layerId", "layerName"])];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2150,10 +2060,7 @@ export class LayerObjectsToGroupCommand extends Command {
|
||||
restorePosition = { ...originalPosition };
|
||||
} else {
|
||||
// 计算对象在画布中的当前绝对位置
|
||||
restorePosition = this._calculateObjectAbsolutePosition(
|
||||
obj,
|
||||
groupObject
|
||||
);
|
||||
restorePosition = this._calculateObjectAbsolutePosition(obj, groupObject);
|
||||
}
|
||||
|
||||
objectsToRestore.push({
|
||||
@@ -2208,9 +2115,7 @@ export class LayerObjectsToGroupCommand extends Command {
|
||||
restoredObjects.push(obj);
|
||||
|
||||
console.log(
|
||||
`✅ 恢复原始对象 ${obj.id || obj.type} 到位置 (${position.left}, ${
|
||||
position.top
|
||||
})`
|
||||
`✅ 恢复原始对象 ${obj.id || obj.type} 到位置 (${position.left}, ${position.top})`
|
||||
);
|
||||
} catch (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 absolutePoint = fabric.util.transformPoint(
|
||||
objectPoint,
|
||||
groupTransform
|
||||
);
|
||||
const absolutePoint = fabric.util.transformPoint(objectPoint, groupTransform);
|
||||
|
||||
// 计算缩放比例
|
||||
const totalScaleX = (group.scaleX || 1) * (obj.scaleX || 1);
|
||||
@@ -2441,8 +2343,7 @@ export class CreateImageLayerCommand extends Command {
|
||||
|
||||
// 存储执行过程中的结果
|
||||
this.newLayerId =
|
||||
generateId("layer_image_") ||
|
||||
`layer_image_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
generateId("layer_image_") || `layer_image_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
this.commands = [];
|
||||
this.executedCommands = [];
|
||||
}
|
||||
@@ -2457,8 +2358,7 @@ export class CreateImageLayerCommand extends Command {
|
||||
this.executedCommands = [];
|
||||
|
||||
// 生成图层名称
|
||||
const fileName =
|
||||
this.layerName || `图片 ${new Date().toLocaleTimeString()}`;
|
||||
const fileName = this.layerName || `图片 ${new Date().toLocaleTimeString()}`;
|
||||
|
||||
this.fabricImage.set({
|
||||
id: this.imageId,
|
||||
@@ -2529,9 +2429,7 @@ export class CreateImageLayerCommand extends Command {
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`↩️ 开始撤销创建图片图层操作,共 ${this.executedCommands.length} 个子命令`
|
||||
);
|
||||
console.log(`↩️ 开始撤销创建图片图层操作,共 ${this.executedCommands.length} 个子命令`);
|
||||
|
||||
try {
|
||||
// 逆序撤销已执行的命令
|
||||
@@ -2547,10 +2445,7 @@ export class CreateImageLayerCommand extends Command {
|
||||
}
|
||||
console.log(`✅ 子命令撤销成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 子命令撤销失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
console.error(`❌ 子命令撤销失败: ${command.constructor.name}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2583,10 +2478,7 @@ export class CreateImageLayerCommand extends Command {
|
||||
}
|
||||
console.log(`✅ 子命令回滚成功: ${command.constructor.name}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ 子命令回滚失败: ${command.constructor.name}`,
|
||||
error
|
||||
);
|
||||
console.error(`❌ 子命令回滚失败: ${command.constructor.name}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2721,9 +2613,7 @@ export class ReorderChildLayersCommand extends Command {
|
||||
|
||||
async execute() {
|
||||
// 查找父图层
|
||||
const parentLayer = this.layers.value.find(
|
||||
(layer) => layer.id === this.parentId
|
||||
);
|
||||
const parentLayer = this.layers.value.find((layer) => layer.id === this.parentId);
|
||||
if (!parentLayer) return false;
|
||||
|
||||
// 获取子图层
|
||||
@@ -2873,15 +2763,11 @@ export class CutLayerCommand extends Command {
|
||||
|
||||
// 生成新图层ID和名称
|
||||
this.newLayerId =
|
||||
generateId("layer_") ||
|
||||
`layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
generateId("layer_") || `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
|
||||
const sourceLayer = layer;
|
||||
const parentLayer = parent;
|
||||
|
||||
@@ -2908,9 +2794,7 @@ export class CutLayerCommand extends Command {
|
||||
blendMode: sourceLayer.blendMode,
|
||||
fabricObjects: [],
|
||||
children: sourceLayer.children ? [...sourceLayer.children] : [],
|
||||
layerProperties: sourceLayer.layerProperties
|
||||
? { ...sourceLayer.layerProperties }
|
||||
: {},
|
||||
layerProperties: sourceLayer.layerProperties ? { ...sourceLayer.layerProperties } : {},
|
||||
metadata: sourceLayer.metadata ? { ...sourceLayer.metadata } : {},
|
||||
parentId: parentLayer ? parentLayer.id : undefined, // 保持父图层关系
|
||||
});
|
||||
@@ -2920,11 +2804,8 @@ export class CutLayerCommand extends Command {
|
||||
|
||||
if (parentLayer) {
|
||||
// 处理子图层:在父图层的children数组中插入
|
||||
const sourceChildIndex = parentLayer.children.findIndex(
|
||||
(child) => child.id === this.layerId
|
||||
);
|
||||
insertIndex =
|
||||
this.insertIndex !== null ? this.insertIndex : sourceChildIndex + 1;
|
||||
const sourceChildIndex = parentLayer.children.findIndex((child) => child.id === this.layerId);
|
||||
insertIndex = this.insertIndex !== null ? this.insertIndex : sourceChildIndex + 1;
|
||||
|
||||
// 插入到父图层的children数组中
|
||||
parentLayer.children.splice(insertIndex, 0, this.newLayer);
|
||||
@@ -2933,11 +2814,8 @@ export class CutLayerCommand extends Command {
|
||||
this.childInsertIndex = insertIndex;
|
||||
} else {
|
||||
// 处理主图层:在主图层数组中插入
|
||||
const sourceIndex = this.layers.value.findIndex(
|
||||
(l) => l.id === this.layerId
|
||||
);
|
||||
insertIndex =
|
||||
this.insertIndex !== null ? this.insertIndex : sourceIndex + 1;
|
||||
const sourceIndex = this.layers.value.findIndex((l) => l.id === this.layerId);
|
||||
insertIndex = this.insertIndex !== null ? this.insertIndex : sourceIndex + 1;
|
||||
|
||||
// 插入到主图层数组中
|
||||
this.layers.value.splice(insertIndex, 0, this.newLayer);
|
||||
@@ -2956,9 +2834,7 @@ export class CutLayerCommand extends Command {
|
||||
// 重新渲染画布
|
||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||
|
||||
console.log(
|
||||
`已复制图层:${newName} ${this.isChildLayer ? "(子图层)" : "(主图层)"}`
|
||||
);
|
||||
console.log(`已复制图层:${newName} ${this.isChildLayer ? "(子图层)" : "(主图层)"}`);
|
||||
return this.newLayerId;
|
||||
}
|
||||
|
||||
@@ -2975,9 +2851,7 @@ export class CutLayerCommand extends Command {
|
||||
}
|
||||
} else {
|
||||
// 撤销主图层:从主图层数组中删除
|
||||
const index = this.layers.value.findIndex(
|
||||
(l) => l.id === this.newLayerId
|
||||
);
|
||||
const index = this.layers.value.findIndex((l) => l.id === this.newLayerId);
|
||||
if (index !== -1) {
|
||||
this.layers.value.splice(index, 1);
|
||||
}
|
||||
@@ -3007,9 +2881,7 @@ export class CutLayerCommand extends Command {
|
||||
fabric.util.enlivenObjects(serializedObjects, (objects) => {
|
||||
objects.forEach((obj) => {
|
||||
// 生成新的对象ID
|
||||
const newObjId = `obj_${Date.now()}_${Math.floor(
|
||||
Math.random() * 1000
|
||||
)}`;
|
||||
const newObjId = `obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
obj.id = newObjId;
|
||||
obj.layerId = this.newLayerId;
|
||||
obj.layerName = this.newLayer.name;
|
||||
@@ -3066,8 +2938,7 @@ export class CreateAdjustmentLayerCommand extends Command {
|
||||
this.newLayer = null;
|
||||
// 生成新图层ID
|
||||
this.newLayerId =
|
||||
generateId("adj_layer_") ||
|
||||
`adj_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
generateId("adj_layer_") || `adj_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
}
|
||||
|
||||
execute() {
|
||||
@@ -3089,9 +2960,7 @@ export class CreateAdjustmentLayerCommand extends Command {
|
||||
|
||||
// 计算插入位置
|
||||
const insertIndex =
|
||||
this.insertIndex !== null
|
||||
? this.insertIndex
|
||||
: this._getInsertIndexAboveActiveLayer();
|
||||
this.insertIndex !== null ? this.insertIndex : this._getInsertIndexAboveActiveLayer();
|
||||
|
||||
// 插入新图层
|
||||
this.layers.value.splice(insertIndex, 0, this.newLayer);
|
||||
@@ -3155,9 +3024,7 @@ export class ApplyLayerStyleCommand extends Command {
|
||||
return;
|
||||
}
|
||||
this.layer = layer;
|
||||
this.oldStyles = this.layer
|
||||
? { ...(this.layer.layerProperties?.styles || {}) }
|
||||
: {};
|
||||
this.oldStyles = this.layer ? { ...(this.layer.layerProperties?.styles || {}) } : {};
|
||||
}
|
||||
|
||||
execute() {
|
||||
@@ -3296,9 +3163,7 @@ export class LayerClippingMaskCommand extends Command {
|
||||
|
||||
if (this.maskLayerId) {
|
||||
// 创建剪贴蒙版
|
||||
const maskLayer = this.layers.value.find(
|
||||
(l) => l.id === this.maskLayerId
|
||||
);
|
||||
const maskLayer = this.layers.value.find((l) => l.id === this.maskLayerId);
|
||||
if (!maskLayer) {
|
||||
console.error(`蒙版图层 ${this.maskLayerId} 不存在`);
|
||||
return false;
|
||||
@@ -3525,19 +3390,14 @@ export class ChangeFixedImageCommand extends Command {
|
||||
this.retryCount = attempt;
|
||||
|
||||
if (attempt === this.maxRetries) {
|
||||
throw new Error(
|
||||
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
|
||||
);
|
||||
throw new Error(`图像加载失败,已重试${this.maxRetries}次: ${error.message}`);
|
||||
}
|
||||
|
||||
// 指数退避重试
|
||||
const delay = Math.pow(2, attempt) * 1000;
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
|
||||
console.warn(
|
||||
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
|
||||
error.message
|
||||
);
|
||||
console.warn(`图像加载重试 ${attempt + 1}/${this.maxRetries}:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3545,9 +3405,7 @@ export class ChangeFixedImageCommand extends Command {
|
||||
loadImage() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(
|
||||
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
|
||||
);
|
||||
reject(new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`));
|
||||
}, this.timeoutMs);
|
||||
|
||||
fabric.Image.fromURL(
|
||||
@@ -3585,15 +3443,9 @@ export class ChangeFixedImageCommand extends Command {
|
||||
|
||||
// 移除旧对象(如果存在)
|
||||
if (this.previousObjectId) {
|
||||
const { object: oldObject } = findObjectById(
|
||||
this.canvas,
|
||||
this.previousObjectId
|
||||
);
|
||||
const { object: oldObject } = findObjectById(this.canvas, this.previousObjectId);
|
||||
if (oldObject) {
|
||||
const removeSuccess = removeCanvasObjectByObject(
|
||||
this.canvas,
|
||||
oldObject
|
||||
);
|
||||
const removeSuccess = removeCanvasObjectByObject(this.canvas, oldObject);
|
||||
if (!removeSuccess) {
|
||||
console.warn("移除旧对象失败,但继续执行");
|
||||
}
|
||||
@@ -3650,97 +3502,75 @@ export class ChangeFixedImageCommand extends Command {
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fabric.util.enlivenObjects(
|
||||
[this.previousImage.objectData],
|
||||
async (objects) => {
|
||||
try {
|
||||
const restoredImage = objects[0];
|
||||
fabric.util.enlivenObjects([this.previousImage.objectData], async (objects) => {
|
||||
try {
|
||||
const restoredImage = objects[0];
|
||||
|
||||
await optimizeCanvasRendering(this.canvas, async () => {
|
||||
// 移除当前对象
|
||||
if (this.newObjectId) {
|
||||
const { object: currentObject } = findObjectById(
|
||||
this.canvas,
|
||||
this.newObjectId
|
||||
);
|
||||
if (currentObject) {
|
||||
removeCanvasObjectByObject(this.canvas, currentObject);
|
||||
}
|
||||
await optimizeCanvasRendering(this.canvas, async () => {
|
||||
// 移除当前对象
|
||||
if (this.newObjectId) {
|
||||
const { object: currentObject } = findObjectById(this.canvas, this.newObjectId);
|
||||
if (currentObject) {
|
||||
removeCanvasObjectByObject(this.canvas, currentObject);
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复之前的变换
|
||||
if (this.previousTransform) {
|
||||
restoredImage.set(this.previousTransform);
|
||||
}
|
||||
// 恢复之前的变换
|
||||
if (this.previousTransform) {
|
||||
restoredImage.set(this.previousTransform);
|
||||
}
|
||||
|
||||
// 设置图层属性
|
||||
restoredImage.set({
|
||||
id: this.previousObjectId,
|
||||
layerId: this.targetLayer.id,
|
||||
layerName: this.targetLayer.name,
|
||||
isBackground: this.targetLayer.isBackground,
|
||||
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
|
||||
);
|
||||
// 设置图层属性
|
||||
restoredImage.set({
|
||||
id: this.previousObjectId,
|
||||
layerId: this.targetLayer.id,
|
||||
layerName: this.targetLayer.name,
|
||||
isBackground: this.targetLayer.isBackground,
|
||||
isFixed: this.targetLayer.isFixed,
|
||||
});
|
||||
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
// 使用帮助函数在正确的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();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async removeCurrentImage() {
|
||||
await optimizeCanvasRendering(this.canvas, async () => {
|
||||
if (this.newObjectId) {
|
||||
const { object: currentObject } = findObjectById(
|
||||
this.canvas,
|
||||
this.newObjectId
|
||||
);
|
||||
const { object: currentObject } = findObjectById(this.canvas, this.newObjectId);
|
||||
if (currentObject) {
|
||||
const removeSuccess = removeCanvasObjectByObject(
|
||||
this.canvas,
|
||||
currentObject
|
||||
);
|
||||
const removeSuccess = removeCanvasObjectByObject(this.canvas, currentObject);
|
||||
if (removeSuccess) {
|
||||
this.targetLayer.fabricObject = null;
|
||||
this.layerManager.updateLayerObject(this.targetLayer.id, null);
|
||||
@@ -3797,10 +3627,7 @@ export class AddImageToLayerCommand extends Command {
|
||||
this.validateInputs();
|
||||
|
||||
// 查找目标图层
|
||||
const { layer } = findLayerRecursively(
|
||||
this.layerManager?.layers?.value || [],
|
||||
this.layerId
|
||||
);
|
||||
const { layer } = findLayerRecursively(this.layerManager?.layers?.value || [], this.layerId);
|
||||
|
||||
this.targetLayer = layer;
|
||||
|
||||
@@ -3859,10 +3686,7 @@ export class AddImageToLayerCommand extends Command {
|
||||
this.canvas.remove(this.addedObject);
|
||||
|
||||
// 从图层管理器中移除
|
||||
this.layerManager.removeObjectFromLayer(
|
||||
this.addedObject.id,
|
||||
this.layerId
|
||||
);
|
||||
this.layerManager.removeObjectFromLayer(this.addedObject.id, this.layerId);
|
||||
|
||||
this.isExecuted = false;
|
||||
|
||||
@@ -3925,19 +3749,14 @@ export class AddImageToLayerCommand extends Command {
|
||||
this.retryCount = attempt;
|
||||
|
||||
if (attempt === this.maxRetries) {
|
||||
throw new Error(
|
||||
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
|
||||
);
|
||||
throw new Error(`图像加载失败,已重试${this.maxRetries}次: ${error.message}`);
|
||||
}
|
||||
|
||||
// 指数退避重试
|
||||
const delay = Math.pow(2, attempt) * 1000;
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
|
||||
console.warn(
|
||||
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
|
||||
error.message
|
||||
);
|
||||
console.warn(`图像加载重试 ${attempt + 1}/${this.maxRetries}:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3945,9 +3764,7 @@ export class AddImageToLayerCommand extends Command {
|
||||
loadImage() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(
|
||||
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
|
||||
);
|
||||
reject(new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`));
|
||||
}, this.timeoutMs);
|
||||
|
||||
fabric.Image.fromURL(
|
||||
@@ -4057,9 +3874,7 @@ export class RemoveChildLayerCommand extends Command {
|
||||
this.parentLayer = parent;
|
||||
|
||||
this.childIndex =
|
||||
this.parentLayer?.children?.findIndex(
|
||||
(child) => child.id === this.layerId
|
||||
) ?? -1;
|
||||
this.parentLayer?.children?.findIndex((child) => child.id === this.layerId) ?? -1;
|
||||
this.removedChild = this.parentLayer?.children?.[this.childIndex];
|
||||
this.isActiveLayer = this.layerId === this.activeLayerId.value;
|
||||
|
||||
@@ -4074,10 +3889,7 @@ export class RemoveChildLayerCommand extends Command {
|
||||
}
|
||||
|
||||
// 从画布中移除子图层中的所有对象
|
||||
if (
|
||||
this.removedChild.fabricObjects &&
|
||||
this.removedChild.fabricObjects.length > 0
|
||||
) {
|
||||
if (this.removedChild.fabricObjects && this.removedChild.fabricObjects.length > 0) {
|
||||
this.removedChild.fabricObjects.forEach((obj) => {
|
||||
const { object } = findObjectById(this.canvas, obj.id);
|
||||
if (object) {
|
||||
@@ -4096,9 +3908,7 @@ export class RemoveChildLayerCommand extends Command {
|
||||
this.activeLayerId.value = this.parentLayer.children[0].id;
|
||||
} else {
|
||||
this.activeLayerId.value =
|
||||
this.layers.value.find(
|
||||
(layer) => !layer.isBackground || !layer.isFixed
|
||||
)?.[0]?.id || null;
|
||||
this.layers.value.find((layer) => !layer.isBackground || !layer.isFixed)?.[0]?.id || null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4233,10 +4043,7 @@ export class ChildLayerLockCommand extends Command {
|
||||
this.layerManager = options.layerManager;
|
||||
|
||||
// 查找父图层和子图层
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
|
||||
this.parentLayer = parent;
|
||||
this.childLayer = layer;
|
||||
this.oldLocked = this.childLayer ? this.childLayer.locked : null;
|
||||
|
||||
@@ -21,9 +21,7 @@ export class LiquifyCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: options.name || `液化操作: ${options.mode || "未知模式"}`,
|
||||
description:
|
||||
options.description ||
|
||||
`使用${options.mode || "未知模式"}模式进行液化操作`,
|
||||
description: options.description || `使用${options.mode || "未知模式"}模式进行液化操作`,
|
||||
});
|
||||
|
||||
this.canvas = options.canvas;
|
||||
@@ -96,9 +94,7 @@ export class LiquifyCommand extends Command {
|
||||
// 对于图像对象,我们需要保存src和元数据
|
||||
const state = {
|
||||
src: this.targetObject.getSrc ? this.targetObject.getSrc() : null,
|
||||
element: this.targetObject._element
|
||||
? this.targetObject._element.cloneNode(true)
|
||||
: null,
|
||||
element: this.targetObject._element ? this.targetObject._element.cloneNode(true) : null,
|
||||
filters: this.targetObject.filters ? [...this.targetObject.filters] : [],
|
||||
originalData: this.originalData,
|
||||
targetLayerId: this.targetLayerId,
|
||||
@@ -172,10 +168,7 @@ export class LiquifyCommand extends Command {
|
||||
// 确保图层引用更新
|
||||
const layer = this.layerManager.getLayerById(this.targetLayerId);
|
||||
if (layer) {
|
||||
if (
|
||||
layer.type === "background" &&
|
||||
layer.fabricObject === this.targetObject
|
||||
) {
|
||||
if (layer.type === "background" && layer.fabricObject === this.targetObject) {
|
||||
layer.fabricObject = img;
|
||||
} else if (layer.fabricObjects) {
|
||||
const objIndex = layer.fabricObjects.indexOf(this.targetObject);
|
||||
@@ -251,9 +244,7 @@ export class LiquifyDeformCommand extends LiquifyCommand {
|
||||
super({
|
||||
...options,
|
||||
name: `液化变形: ${options.mode || "未知模式"}`,
|
||||
description: `在(${options.x}, ${options.y})应用${
|
||||
options.mode || "未知模式"
|
||||
}变形`,
|
||||
description: `在(${options.x}, ${options.y})应用${options.mode || "未知模式"}变形`,
|
||||
});
|
||||
|
||||
this.x = options.x;
|
||||
@@ -343,9 +334,7 @@ export class LiquifyDeformCommand extends LiquifyCommand {
|
||||
layer.fabricObject = img;
|
||||
} else if (layer.fabricObjects) {
|
||||
const objIndex = layer.fabricObjects.findIndex(
|
||||
(obj) =>
|
||||
obj === this.savedState?.originalObject ||
|
||||
obj === this.targetObject
|
||||
(obj) => obj === this.savedState?.originalObject || obj === this.targetObject
|
||||
);
|
||||
if (objIndex !== -1) {
|
||||
layer.fabricObjects[objIndex] = img;
|
||||
@@ -374,8 +363,7 @@ export class CompositeLiquifyCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: options.name || "液化操作组合",
|
||||
description:
|
||||
options.description || `包含${options.commands?.length || 0}个液化操作`,
|
||||
description: options.description || `包含${options.commands?.length || 0}个液化操作`,
|
||||
});
|
||||
|
||||
this.commands = options.commands || [];
|
||||
@@ -551,10 +539,7 @@ export class LiquifyStateCommand extends Command {
|
||||
}
|
||||
|
||||
// 通过引用管理器更新图像数据
|
||||
await this.refManager.updateObjectImageData(
|
||||
this.objectRefId,
|
||||
this.finalImageData
|
||||
);
|
||||
await this.refManager.updateObjectImageData(this.objectRefId, this.finalImageData);
|
||||
|
||||
// 恢复液化管理器到最终状态
|
||||
if (this.liquifyManager && this.finalLiquifyState) {
|
||||
@@ -585,10 +570,7 @@ export class LiquifyStateCommand extends Command {
|
||||
}
|
||||
|
||||
// 通过引用管理器恢复到初始快照
|
||||
await this.refManager.restoreFromSnapshot(
|
||||
this.objectRefId,
|
||||
this.initialSnapshotId
|
||||
);
|
||||
await this.refManager.restoreFromSnapshot(this.objectRefId, this.initialSnapshotId);
|
||||
|
||||
// 恢复液化管理器到初始状态 - 关键修复
|
||||
if (this.liquifyManager) {
|
||||
@@ -604,18 +586,16 @@ export class LiquifyStateCommand extends Command {
|
||||
|
||||
// 强制设置原始图像数据为初始状态
|
||||
if (this.liquifyManager.enhancedManager) {
|
||||
this.liquifyManager.enhancedManager.originalImageData =
|
||||
this.initialImageData;
|
||||
this.liquifyManager.enhancedManager.currentImageData =
|
||||
this._cloneImageData(this.initialImageData);
|
||||
this.liquifyManager.enhancedManager.originalImageData = this.initialImageData;
|
||||
this.liquifyManager.enhancedManager.currentImageData = this._cloneImageData(
|
||||
this.initialImageData
|
||||
);
|
||||
|
||||
// 重置激活渲染器的数据
|
||||
if (this.liquifyManager.enhancedManager.activeRenderer) {
|
||||
const renderer = this.liquifyManager.enhancedManager.activeRenderer;
|
||||
renderer.originalImageData = this.initialImageData;
|
||||
renderer.currentImageData = this._cloneImageData(
|
||||
this.initialImageData
|
||||
);
|
||||
renderer.currentImageData = this._cloneImageData(this.initialImageData);
|
||||
|
||||
// 重置CPU渲染器的网格和变形历史
|
||||
if (renderer.reset) {
|
||||
@@ -741,8 +721,7 @@ export class LiquifyStateCommand extends Command {
|
||||
timestamp: Date.now(),
|
||||
properties: this.refManager._captureObjectState(fabricObject),
|
||||
imageData: this.initialImageData,
|
||||
eventListeners:
|
||||
this.refManager.eventListeners.get(this.objectRefId) || {},
|
||||
eventListeners: this.refManager.eventListeners.get(this.objectRefId) || {},
|
||||
};
|
||||
|
||||
this.refManager.stateSnapshots.set(this.initialSnapshotId, snapshot);
|
||||
@@ -772,8 +751,7 @@ export class LiquifyStateCommand extends Command {
|
||||
timestamp: Date.now(),
|
||||
properties: this.refManager._captureObjectState(fabricObject),
|
||||
imageData: this.finalImageData,
|
||||
eventListeners:
|
||||
this.refManager.eventListeners.get(this.objectRefId) || {},
|
||||
eventListeners: this.refManager.eventListeners.get(this.objectRefId) || {},
|
||||
};
|
||||
|
||||
this.refManager.stateSnapshots.set(this.finalSnapshotId, snapshot);
|
||||
@@ -804,8 +782,7 @@ export class LiquifyStateCommand extends Command {
|
||||
// 捕获当前渲染器状态
|
||||
activeRendererState: null,
|
||||
// 捕获目标对象引用
|
||||
targetObjectRef:
|
||||
this.liquifyManager.targetObject ?? enhancedManager.targetObject,
|
||||
targetObjectRef: this.liquifyManager.targetObject ?? enhancedManager.targetObject,
|
||||
// 捕获初始化状态
|
||||
initialized: this.liquifyManager.initialized || false,
|
||||
};
|
||||
@@ -832,13 +809,9 @@ export class LiquifyStateCommand extends Command {
|
||||
params: { ...renderer.params },
|
||||
currentMode: renderer.currentMode,
|
||||
// 对于CPU渲染器,还需要保存网格状态
|
||||
meshState: renderer.mesh
|
||||
? this._captureMeshState(renderer.mesh)
|
||||
: null,
|
||||
meshState: renderer.mesh ? this._captureMeshState(renderer.mesh) : null,
|
||||
// 保存变形历史
|
||||
deformHistory: renderer.deformHistory
|
||||
? [...renderer.deformHistory]
|
||||
: [],
|
||||
deformHistory: renderer.deformHistory ? [...renderer.deformHistory] : [],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -871,9 +844,7 @@ export class LiquifyStateCommand extends Command {
|
||||
this.realtimeUpdater.targetObject = this.liquifyManager.targetObject;
|
||||
// 如果有setTargetObject方法,调用它
|
||||
if (typeof this.realtimeUpdater.setTargetObject === "function") {
|
||||
this.realtimeUpdater.setTargetObject(
|
||||
this.liquifyManager.targetObject
|
||||
);
|
||||
this.realtimeUpdater.setTargetObject(this.liquifyManager.targetObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -911,10 +882,8 @@ export class LiquifyStateCommand extends Command {
|
||||
} else if (renderer.mesh && renderer._initMesh) {
|
||||
// 如果没有保存的网格状态,重新初始化网格
|
||||
renderer._initMesh(
|
||||
rendererState.originalImageData?.width ||
|
||||
renderer.originalImageData?.width,
|
||||
rendererState.originalImageData?.height ||
|
||||
renderer.originalImageData?.height
|
||||
rendererState.originalImageData?.width || renderer.originalImageData?.width,
|
||||
rendererState.originalImageData?.height || renderer.originalImageData?.height
|
||||
);
|
||||
}
|
||||
|
||||
@@ -978,13 +947,7 @@ export class LiquifyStateCommand extends Command {
|
||||
* @private
|
||||
*/
|
||||
_restoreMeshState(mesh, meshState) {
|
||||
if (
|
||||
!mesh ||
|
||||
!meshState ||
|
||||
!Array.isArray(mesh) ||
|
||||
!Array.isArray(meshState)
|
||||
)
|
||||
return;
|
||||
if (!mesh || !meshState || !Array.isArray(mesh) || !Array.isArray(meshState)) return;
|
||||
|
||||
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++) {
|
||||
@@ -1043,8 +1006,7 @@ export class BatchLiquifyStateCommand extends Command {
|
||||
constructor(options) {
|
||||
super({
|
||||
name: options.name || "批量液化操作",
|
||||
description:
|
||||
options.description || `批量液化${options.commands?.length || 0}个对象`,
|
||||
description: options.description || `批量液化${options.commands?.length || 0}个对象`,
|
||||
});
|
||||
|
||||
this.commands = options.commands || [];
|
||||
@@ -1117,9 +1079,7 @@ export class BatchLiquifyStateCommand extends Command {
|
||||
* @returns {Boolean} 是否有效
|
||||
*/
|
||||
isValid() {
|
||||
return (
|
||||
this.commands.length > 0 && this.commands.every((cmd) => cmd.isValid())
|
||||
);
|
||||
return this.commands.length > 0 && this.commands.every((cmd) => cmd.isValid());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,11 +34,7 @@ export class SetActiveLayerCommand extends Command {
|
||||
}
|
||||
|
||||
execute() {
|
||||
const { layer } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId,
|
||||
this.parentId
|
||||
);
|
||||
const { layer } = findLayerRecursively(this.layers.value, this.layerId, this.parentId);
|
||||
this.newLayer = layer;
|
||||
|
||||
if (!this.newLayer) {
|
||||
@@ -68,10 +64,7 @@ export class SetActiveLayerCommand extends Command {
|
||||
this.canvas.discardActiveObject();
|
||||
|
||||
// 设置为新的图层下的对象为激活,但需要确保对象存在于画布上
|
||||
if (
|
||||
this.newLayer.fabricObjects &&
|
||||
this.newLayer.fabricObjects.length > 0
|
||||
) {
|
||||
if (this.newLayer.fabricObjects && this.newLayer.fabricObjects.length > 0) {
|
||||
const canvasObjects = this.canvas.getObjects();
|
||||
const validObjects = this.newLayer.fabricObjects.filter(
|
||||
(obj) => obj && canvasObjects.includes(obj)
|
||||
@@ -153,9 +146,7 @@ export class AddObjectToLayerCommand extends Command {
|
||||
this.layerManager = options.layerManager;
|
||||
|
||||
// 保存对象原始状态和ID
|
||||
this.objectId =
|
||||
this.fabricObject.id ||
|
||||
`obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
this.objectId = this.fabricObject.id || `obj_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
this.fabricObject.set({
|
||||
id: this.objectId,
|
||||
layerId: options.layerId,
|
||||
@@ -163,11 +154,7 @@ export class AddObjectToLayerCommand extends Command {
|
||||
});
|
||||
|
||||
// 保存完整的对象状态,包括位置和变换信息
|
||||
this.originalObjectState = this.fabricObject.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
]);
|
||||
this.originalObjectState = this.fabricObject.toObject(["id", "layerId", "layerName"]);
|
||||
|
||||
// // 新增:保存详细的位置和变换信息,用于重做时恢复
|
||||
// this.originalPosition = {
|
||||
@@ -279,8 +266,7 @@ export class AddObjectToLayerCommand extends Command {
|
||||
// 将对象添加到图层的fabricObjects数组
|
||||
layer.fabricObjects = layer.fabricObjects || [];
|
||||
layer.fabricObjects.push(
|
||||
this.fabricObject?.toObject?.(["id", "layerId", "layerName"]) ||
|
||||
this.fabricObject
|
||||
this.fabricObject?.toObject?.(["id", "layerId", "layerName"]) || this.fabricObject
|
||||
);
|
||||
|
||||
await this.layerManager?.updateLayersObjectsInteractivity?.(false);
|
||||
@@ -327,16 +313,12 @@ export class AddObjectToLayerCommand extends Command {
|
||||
|
||||
// 从图层的fabricObjects数组中移除对象
|
||||
if (layer.fabricObjects) {
|
||||
layer.fabricObjects = layer.fabricObjects.filter(
|
||||
(obj) => obj.id !== this.objectId
|
||||
);
|
||||
layer.fabricObjects = layer.fabricObjects.filter((obj) => obj.id !== this.objectId);
|
||||
}
|
||||
// 优化渲染 - 统一批处理 支持异步回调
|
||||
optimizeCanvasRendering(this.canvas, async () => {
|
||||
// 从画布移除对象
|
||||
const object = this.canvas
|
||||
.getObjects()
|
||||
.find((obj) => obj.id === this.objectId);
|
||||
const object = this.canvas.getObjects().find((obj) => obj.id === this.objectId);
|
||||
if (object) {
|
||||
// 先丢弃活动对象,避免控制点渲染错误
|
||||
this.canvas.discardActiveObject();
|
||||
@@ -410,9 +392,7 @@ export class RemoveObjectFromLayerCommand extends Command {
|
||||
|
||||
// 从图层的fabricObjects数组移除对象
|
||||
if (layer.fabricObjects) {
|
||||
layer.fabricObjects = layer.fabricObjects.filter(
|
||||
(obj) => obj.id !== this.objectId
|
||||
);
|
||||
layer.fabricObjects = layer.fabricObjects.filter((obj) => obj.id !== this.objectId);
|
||||
}
|
||||
|
||||
// 更新画布
|
||||
@@ -639,19 +619,14 @@ export class ChangeFixedImageCommand extends Command {
|
||||
this.retryCount = attempt;
|
||||
|
||||
if (attempt === this.maxRetries) {
|
||||
throw new Error(
|
||||
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
|
||||
);
|
||||
throw new Error(`图像加载失败,已重试${this.maxRetries}次: ${error.message}`);
|
||||
}
|
||||
|
||||
// 指数退避重试
|
||||
const delay = Math.pow(2, attempt) * 1000;
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
|
||||
console.warn(
|
||||
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
|
||||
error.message
|
||||
);
|
||||
console.warn(`图像加载重试 ${attempt + 1}/${this.maxRetries}:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -659,9 +634,7 @@ export class ChangeFixedImageCommand extends Command {
|
||||
loadImage() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(
|
||||
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
|
||||
);
|
||||
reject(new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`));
|
||||
}, this.timeoutMs);
|
||||
|
||||
fabric.Image.fromURL(
|
||||
@@ -697,15 +670,9 @@ export class ChangeFixedImageCommand extends Command {
|
||||
|
||||
// 移除旧对象(如果存在)
|
||||
if (this.previousObjectId) {
|
||||
const { object: oldObject } = findObjectById(
|
||||
this.canvas,
|
||||
this.previousObjectId
|
||||
);
|
||||
const { object: oldObject } = findObjectById(this.canvas, this.previousObjectId);
|
||||
if (oldObject) {
|
||||
const removeSuccess = removeCanvasObjectByObject(
|
||||
this.canvas,
|
||||
oldObject
|
||||
);
|
||||
const removeSuccess = removeCanvasObjectByObject(this.canvas, oldObject);
|
||||
if (!removeSuccess) {
|
||||
console.warn("移除旧对象失败,但继续执行");
|
||||
}
|
||||
@@ -771,93 +738,75 @@ export class ChangeFixedImageCommand extends Command {
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fabric.util.enlivenObjects(
|
||||
[this.previousImage.objectData],
|
||||
async (objects) => {
|
||||
try {
|
||||
const restoredImage = objects[0];
|
||||
fabric.util.enlivenObjects([this.previousImage.objectData], async (objects) => {
|
||||
try {
|
||||
const restoredImage = objects[0];
|
||||
|
||||
await optimizeCanvasRendering(this.canvas, async () => {
|
||||
// 移除当前对象
|
||||
if (this.newObjectId) {
|
||||
const { object: currentObject } = findObjectById(
|
||||
this.canvas,
|
||||
this.newObjectId
|
||||
);
|
||||
if (currentObject) {
|
||||
removeCanvasObjectByObject(this.canvas, currentObject);
|
||||
}
|
||||
await optimizeCanvasRendering(this.canvas, async () => {
|
||||
// 移除当前对象
|
||||
if (this.newObjectId) {
|
||||
const { object: currentObject } = findObjectById(this.canvas, this.newObjectId);
|
||||
if (currentObject) {
|
||||
removeCanvasObjectByObject(this.canvas, currentObject);
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复之前的变换
|
||||
if (this.previousTransform) {
|
||||
restoredImage.set(this.previousTransform);
|
||||
}
|
||||
// 恢复之前的变换
|
||||
if (this.previousTransform) {
|
||||
restoredImage.set(this.previousTransform);
|
||||
}
|
||||
|
||||
// 设置图层属性
|
||||
restoredImage.set({
|
||||
id: this.previousObjectId,
|
||||
layerId: this.targetLayer.id,
|
||||
layerName: this.targetLayer.name,
|
||||
isBackground: this.targetLayer.isBackground,
|
||||
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
|
||||
);
|
||||
// 设置图层属性
|
||||
restoredImage.set({
|
||||
id: this.previousObjectId,
|
||||
layerId: this.targetLayer.id,
|
||||
layerName: this.targetLayer.name,
|
||||
isBackground: this.targetLayer.isBackground,
|
||||
isFixed: this.targetLayer.isFixed,
|
||||
});
|
||||
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
// 使用帮助函数在正确的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();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async removeCurrentImage() {
|
||||
await optimizeCanvasRendering(this.canvas, async () => {
|
||||
if (this.newObjectId) {
|
||||
const { object: currentObject } = findObjectById(
|
||||
this.canvas,
|
||||
this.newObjectId
|
||||
);
|
||||
const { object: currentObject } = findObjectById(this.canvas, this.newObjectId);
|
||||
if (currentObject) {
|
||||
const removeSuccess = removeCanvasObjectByObject(
|
||||
this.canvas,
|
||||
currentObject
|
||||
);
|
||||
const removeSuccess = removeCanvasObjectByObject(this.canvas, currentObject);
|
||||
if (removeSuccess) {
|
||||
this.targetLayer.fabricObject = null;
|
||||
this.layerManager.updateLayerObject(this.targetLayer.id, null);
|
||||
@@ -976,10 +925,7 @@ export class AddImageToLayerCommand extends Command {
|
||||
this.canvas.remove(this.addedObject);
|
||||
|
||||
// 从图层管理器中移除
|
||||
this.layerManager.removeObjectFromLayer(
|
||||
this.addedObject.id,
|
||||
this.layerId
|
||||
);
|
||||
this.layerManager.removeObjectFromLayer(this.addedObject.id, this.layerId);
|
||||
|
||||
this.isExecuted = false;
|
||||
|
||||
@@ -1042,19 +988,14 @@ export class AddImageToLayerCommand extends Command {
|
||||
this.retryCount = attempt;
|
||||
|
||||
if (attempt === this.maxRetries) {
|
||||
throw new Error(
|
||||
`图像加载失败,已重试${this.maxRetries}次: ${error.message}`
|
||||
);
|
||||
throw new Error(`图像加载失败,已重试${this.maxRetries}次: ${error.message}`);
|
||||
}
|
||||
|
||||
// 指数退避重试
|
||||
const delay = Math.pow(2, attempt) * 1000;
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
|
||||
console.warn(
|
||||
`图像加载重试 ${attempt + 1}/${this.maxRetries}:`,
|
||||
error.message
|
||||
);
|
||||
console.warn(`图像加载重试 ${attempt + 1}/${this.maxRetries}:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1062,9 +1003,7 @@ export class AddImageToLayerCommand extends Command {
|
||||
loadImage() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(
|
||||
new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`)
|
||||
);
|
||||
reject(new Error(`图像加载超时 (${this.timeoutMs}ms): ${this.imageUrl}`));
|
||||
}, this.timeoutMs);
|
||||
|
||||
fabric.Image.fromURL(
|
||||
@@ -1318,23 +1257,16 @@ export class MoveLayerToTopCommand extends Command {
|
||||
|
||||
this.parentLayer = null; // 父图层
|
||||
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
|
||||
|
||||
// 如果parent 有值 或者 layer 上有parentId 则视为子图层的置顶
|
||||
if (parent?.id) {
|
||||
// 查找子图层索引
|
||||
this.layerIndex = parent?.children?.findIndex(
|
||||
(layer) => layer.id === this.layerId
|
||||
);
|
||||
this.layerIndex = parent?.children?.findIndex((layer) => layer.id === this.layerId);
|
||||
this.parentLayer = parent;
|
||||
} else {
|
||||
// 查找图层索引
|
||||
this.layerIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.id === this.layerId
|
||||
);
|
||||
this.layerIndex = this.layers.value.findIndex((layer) => layer.id === this.layerId);
|
||||
}
|
||||
|
||||
this.layer = layer;
|
||||
@@ -1416,23 +1348,16 @@ export class MoveLayerToBottomCommand extends Command {
|
||||
|
||||
this.parentLayer = null; // 父图层
|
||||
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
|
||||
|
||||
// 如果parent 有值 或者 layer 上有parentId 则视为子图层的置顶
|
||||
if (parent?.id) {
|
||||
// 查找子图层索引
|
||||
this.layerIndex = parent?.children?.findIndex(
|
||||
(layer) => layer.id === this.layerId
|
||||
);
|
||||
this.layerIndex = parent?.children?.findIndex((layer) => layer.id === this.layerId);
|
||||
this.parentLayer = parent;
|
||||
} else {
|
||||
// 查找图层索引
|
||||
this.layerIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.id === this.layerId
|
||||
);
|
||||
this.layerIndex = this.layers.value.findIndex((layer) => layer.id === this.layerId);
|
||||
}
|
||||
|
||||
this.layer = layer;
|
||||
@@ -1473,9 +1398,7 @@ export class MoveLayerToBottomCommand extends Command {
|
||||
async undo() {
|
||||
if (this.originalIndex !== -1 && this.layer) {
|
||||
// 获取当前位置
|
||||
const currentIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.id === this.layerId
|
||||
);
|
||||
const currentIndex = this.layers.value.findIndex((layer) => layer.id === this.layerId);
|
||||
|
||||
if (currentIndex !== -1) {
|
||||
// 移除当前位置
|
||||
@@ -1526,16 +1449,11 @@ export class MoveLayerToBottomCommand extends Command {
|
||||
|
||||
if (layer.isBackground && layer.fabricObject) {
|
||||
// 背景图层
|
||||
const originalObj = canvasObjects.find(
|
||||
(o) => o.id === layer.fabricObject.id
|
||||
);
|
||||
const originalObj = canvasObjects.find((o) => o.id === layer.fabricObject.id);
|
||||
if (originalObj) {
|
||||
this.canvas.add(originalObj);
|
||||
}
|
||||
} else if (
|
||||
Array.isArray(layer.fabricObjects) &&
|
||||
layer.fabricObjects.length > 0
|
||||
) {
|
||||
} else if (Array.isArray(layer.fabricObjects) && layer.fabricObjects.length > 0) {
|
||||
// 普通图层和固定图层
|
||||
layer.fabricObjects.forEach((obj) => {
|
||||
const originalObj = canvasObjects.find((o) => o.id === obj.id);
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { Command } from "./Command";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import {
|
||||
LayerType,
|
||||
OperationType,
|
||||
createLayer,
|
||||
findLayerRecursively,
|
||||
} from "../utils/layerHelper";
|
||||
import { LayerType, OperationType, createLayer, findLayerRecursively } from "../utils/layerHelper";
|
||||
import {
|
||||
generateId,
|
||||
optimizeCanvasRendering,
|
||||
@@ -39,10 +34,7 @@ export class RasterizeLayerCommand extends Command {
|
||||
this.layerManager = options.layerManager;
|
||||
|
||||
// 查找目标图层
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
|
||||
this.layer = layer;
|
||||
this.parentLayer = parent;
|
||||
this.isGroupLayer = this.layer?.children && this.layer.children.length > 0;
|
||||
@@ -102,9 +94,7 @@ export class RasterizeLayerCommand extends Command {
|
||||
|
||||
console.log(`✅ 图层 ${this.layer.name} 栅格化完成`);
|
||||
|
||||
this.canvas?.thumbnailManager?.generateLayerThumbnail?.(
|
||||
this.rasterizedLayerId
|
||||
);
|
||||
this.canvas?.thumbnailManager?.generateLayerThumbnail?.(this.rasterizedLayerId);
|
||||
return this.rasterizedLayerId;
|
||||
} catch (error) {
|
||||
console.error("栅格化图层失败:", error);
|
||||
@@ -350,10 +340,7 @@ export class RasterizeLayerCommand extends Command {
|
||||
*/
|
||||
_replaceLayerInStructure() {
|
||||
// 1.当前如果是子图层,则插入到子图层的位置
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
|
||||
|
||||
let insertIndex = 0;
|
||||
// 说明是子图层
|
||||
@@ -374,11 +361,7 @@ export class RasterizeLayerCommand extends Command {
|
||||
if (insertIndex !== -1) {
|
||||
if (parent) {
|
||||
const pIndex = this.layers.value.findIndex((l) => l.id === parent.id);
|
||||
this.layers.value[pIndex].children?.splice?.(
|
||||
insertIndex,
|
||||
1,
|
||||
this.rasterizedLayer
|
||||
);
|
||||
this.layers.value[pIndex].children?.splice?.(insertIndex, 1, this.rasterizedLayer);
|
||||
} else {
|
||||
this.layers.value.splice(insertIndex, 1, this.rasterizedLayer);
|
||||
}
|
||||
@@ -396,14 +379,9 @@ export class RasterizeLayerCommand extends Command {
|
||||
async _getMaskObject() {
|
||||
// 如果图层有clippingMask,获取对应的fabric对象
|
||||
if (this.layer?.clippingMask) {
|
||||
const { object: maskObject } = findObjectById(
|
||||
this.canvas,
|
||||
this.layer.clippingMask.id
|
||||
);
|
||||
const { object: maskObject } = findObjectById(this.canvas, this.layer.clippingMask.id);
|
||||
if (maskObject) {
|
||||
console.log(
|
||||
`📎 找到遮罩对象: ${maskObject.id}, 类型: ${maskObject.type}`
|
||||
);
|
||||
console.log(`📎 找到遮罩对象: ${maskObject.id}, 类型: ${maskObject.type}`);
|
||||
return maskObject;
|
||||
}
|
||||
return await restoreFabricObject(this.layer.clippingMask);
|
||||
@@ -449,10 +427,7 @@ export class ExportLayerToImageCommand extends Command {
|
||||
this.layerManager = options.layerManager;
|
||||
|
||||
// 查找目标图层
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
this.layerId
|
||||
);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, this.layerId);
|
||||
this.layer = layer;
|
||||
this.parentLayer = parent;
|
||||
this.isGroupLayer = this.layer?.children && this.layer.children.length > 0;
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
findObjectById,
|
||||
generateId,
|
||||
optimizeCanvasRendering,
|
||||
} from "../utils/helper.js";
|
||||
import { findObjectById, generateId, optimizeCanvasRendering } from "../utils/helper.js";
|
||||
import { imageModeHandler } from "../utils/imageHelper.js";
|
||||
import { LayerType, OperationType } from "../utils/layerHelper.js";
|
||||
import { Command, CompositeCommand } from "./Command.js";
|
||||
@@ -58,9 +54,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
const layers = this.layerManager.layers?.value || [];
|
||||
const backgroundLayer = layers.find((layer) => layer.isBackground);
|
||||
const fixedLayer = layers.find((layer) => layer.isFixed);
|
||||
const normalLayers = layers.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
);
|
||||
const normalLayers = layers.filter((layer) => !layer.isBackground && !layer.isFixed);
|
||||
|
||||
if (!backgroundLayer || !fixedLayer || normalLayers.length === 0) {
|
||||
throw new Error("缺少必要的图层结构");
|
||||
@@ -76,9 +70,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
}
|
||||
: null;
|
||||
|
||||
this.originalFixedObjects = fixedLayer.fabricObject
|
||||
? [fixedLayer.fabricObject]
|
||||
: [];
|
||||
this.originalFixedObjects = fixedLayer.fabricObject ? [fixedLayer.fabricObject] : [];
|
||||
|
||||
this.originalNormalObjects = normalLayer.fabricObjects
|
||||
? [...normalLayer.fabricObjects]
|
||||
@@ -118,11 +110,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
await this._setupClothingImage(clothingImg, fixedLayer);
|
||||
|
||||
// 7. 设置红绿图到普通图层,位置和大小与衣服底图一致
|
||||
await this._setupRedGreenImage(
|
||||
redGreenImg,
|
||||
normalLayer,
|
||||
this.clothingImage
|
||||
);
|
||||
await this._setupRedGreenImage(redGreenImg, normalLayer, this.clothingImage);
|
||||
|
||||
// 4. 确保背景图层大小和衣服地图大小一致
|
||||
const backgroundObject = await this._setupBackgroundLayer(
|
||||
@@ -207,13 +195,9 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
async _createAndActivateEmptyLayer() {
|
||||
// 创建新的空白图层
|
||||
const newLayerName = "绘制图层";
|
||||
const newLayerId = await this.layerManager.createLayer(
|
||||
newLayerName,
|
||||
LayerType.BITMAP,
|
||||
{
|
||||
undoable: false,
|
||||
}
|
||||
);
|
||||
const newLayerId = await this.layerManager.createLayer(newLayerName, LayerType.BITMAP, {
|
||||
undoable: false,
|
||||
});
|
||||
|
||||
// 设置为活动图层
|
||||
if (newLayerId) {
|
||||
@@ -228,19 +212,14 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
await optimizeCanvasRendering(this.canvas, async () => {
|
||||
// 1. 恢复画布背景
|
||||
if (this.originalCanvasBackground !== null) {
|
||||
this.canvas.setBackgroundColor(
|
||||
this.originalCanvasBackground,
|
||||
() => {}
|
||||
);
|
||||
this.canvas.setBackgroundColor(this.originalCanvasBackground, () => {});
|
||||
}
|
||||
|
||||
// 2. 恢复图层对象
|
||||
const layers = this.layerManager.layers?.value || [];
|
||||
const backgroundLayer = layers.find((layer) => layer.isBackground);
|
||||
const fixedLayer = layers.find((layer) => layer.isFixed);
|
||||
const normalLayers = layers.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
);
|
||||
const normalLayers = layers.filter((layer) => !layer.isBackground && !layer.isFixed);
|
||||
|
||||
// 移除当前添加的对象
|
||||
if (this.clothingImage) {
|
||||
@@ -252,9 +231,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
|
||||
// 移除新创建的空白图层
|
||||
if (this.newEmptyLayerId) {
|
||||
const emptyLayerIndex = layers.findIndex(
|
||||
(layer) => layer.id === this.newEmptyLayerId
|
||||
);
|
||||
const emptyLayerIndex = layers.findIndex((layer) => layer.id === this.newEmptyLayerId);
|
||||
if (emptyLayerIndex !== -1) {
|
||||
layers.splice(emptyLayerIndex, 1);
|
||||
}
|
||||
@@ -270,9 +247,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
// 恢复固定图层
|
||||
if (fixedLayer) {
|
||||
fixedLayer.fabricObject =
|
||||
this.originalFixedObjects.length > 0
|
||||
? this.originalFixedObjects[0]
|
||||
: null;
|
||||
this.originalFixedObjects.length > 0 ? this.originalFixedObjects[0] : null;
|
||||
|
||||
if (fixedLayer.fabricObject) {
|
||||
this.canvas.add(fixedLayer.fabricObject);
|
||||
@@ -311,8 +286,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
|
||||
// 4. 恢复工具状态
|
||||
if (this.toolManager && this.originalToolState) {
|
||||
this.toolManager.isRedGreenMode =
|
||||
this.originalToolState.isRedGreenMode;
|
||||
this.toolManager.isRedGreenMode = this.originalToolState.isRedGreenMode;
|
||||
if (this.originalToolState.currentTool) {
|
||||
this.toolManager.setTool(this.originalToolState.currentTool);
|
||||
}
|
||||
@@ -434,10 +408,7 @@ export class BatchInitializeRedGreenModeCommand extends Command {
|
||||
|
||||
// 清除固定图层原有内容
|
||||
if (fixedLayer.fabricObject) {
|
||||
const { object } = findObjectById(
|
||||
this.canvas,
|
||||
fixedLayer.fabricObject.id
|
||||
);
|
||||
const { object } = findObjectById(this.canvas, fixedLayer.fabricObject.id);
|
||||
if (object) this.canvas.remove(object);
|
||||
}
|
||||
|
||||
|
||||
@@ -110,9 +110,7 @@ export class AddToSelectionCommand extends Command {
|
||||
}
|
||||
|
||||
// 添加到选区
|
||||
const result = await this.selectionManager.addToSelection(
|
||||
this.newSelection
|
||||
);
|
||||
const result = await this.selectionManager.addToSelection(this.newSelection);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -155,9 +153,7 @@ export class RemoveFromSelectionCommand extends Command {
|
||||
}
|
||||
|
||||
// 从选区中移除
|
||||
const result = await this.selectionManager.removeFromSelection(
|
||||
this.removeSelection
|
||||
);
|
||||
const result = await this.selectionManager.removeFromSelection(this.removeSelection);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -203,9 +199,7 @@ export class FeatherSelectionCommand extends Command {
|
||||
}
|
||||
|
||||
// 应用羽化
|
||||
const result = await this.selectionManager.featherSelection(
|
||||
this.featherAmount
|
||||
);
|
||||
const result = await this.selectionManager.featherSelection(this.featherAmount);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -347,8 +341,7 @@ export class CopySelectionToNewLayerCommand extends CompositeCommand {
|
||||
}
|
||||
|
||||
// 确定源图层
|
||||
const sourceId =
|
||||
this.sourceLayerId || this.layerManager.getActiveLayerId();
|
||||
const sourceId = this.sourceLayerId || this.layerManager.getActiveLayerId();
|
||||
const sourceLayer = this.layerManager.getLayerById(sourceId);
|
||||
if (!sourceLayer || !sourceLayer.fabricObjects) {
|
||||
console.error("无法复制选区:源图层无效或为空");
|
||||
@@ -356,10 +349,7 @@ export class CopySelectionToNewLayerCommand extends CompositeCommand {
|
||||
}
|
||||
|
||||
// 创建新图层
|
||||
this.newLayerId = await this.layerManager.createLayer(
|
||||
this.newLayerName,
|
||||
LayerType.EMPTY
|
||||
);
|
||||
this.newLayerId = await this.layerManager.createLayer(this.newLayerName, LayerType.EMPTY);
|
||||
|
||||
// 获取选区内的对象
|
||||
const objectsToCopy = sourceLayer.fabricObjects.filter((obj) => {
|
||||
|
||||
@@ -401,9 +401,7 @@ export class CreateTextCommand extends Command {
|
||||
// 更新图层的对象列表
|
||||
if (layer) {
|
||||
layer.fabricObjects = layer.fabricObjects || [];
|
||||
layer.fabricObjects.push(
|
||||
this.textObject.toObject(["id", "layerId", "layerName"])
|
||||
);
|
||||
layer.fabricObjects.push(this.textObject.toObject(["id", "layerId", "layerName"]));
|
||||
}
|
||||
|
||||
// 现在可以安全地设置为活动图层
|
||||
@@ -416,9 +414,7 @@ export class CreateTextCommand extends Command {
|
||||
this.layerManager?.toolManager?.setTool?.(OperationType.SELECT);
|
||||
});
|
||||
|
||||
console.log(
|
||||
`✅ 文本对象已创建: "${finalOptions.text}",位置: (${this.x}, ${this.y})`
|
||||
);
|
||||
console.log(`✅ 文本对象已创建: "${finalOptions.text}",位置: (${this.x}, ${this.y})`);
|
||||
return this.textObject;
|
||||
} catch (error) {
|
||||
console.error("创建文本对象失败:", error);
|
||||
@@ -467,15 +463,11 @@ export class CreateTextCommand extends Command {
|
||||
parentLayer.children = parentLayer.children || [];
|
||||
parentLayer.children.splice(insertIndex, 0, newLayer);
|
||||
|
||||
console.log(
|
||||
`新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`
|
||||
);
|
||||
console.log(`新图层已插入到子图层位置: ${insertIndex} (父图层: ${parentLayer.name})`);
|
||||
} else {
|
||||
// 当前激活图层是一级图层
|
||||
// 在一级图层中,插入到激活图层之上
|
||||
const activeLayerIndex = layers.findIndex(
|
||||
(layer) => layer.id === currentActiveLayerId
|
||||
);
|
||||
const activeLayerIndex = layers.findIndex((layer) => layer.id === currentActiveLayerId);
|
||||
insertIndex = Math.max(0, activeLayerIndex);
|
||||
layers.splice(insertIndex, 0, newLayer);
|
||||
|
||||
@@ -551,9 +543,7 @@ export class CreateTextCommand extends Command {
|
||||
// 恢复原活动图层
|
||||
if (this.oldActiveLayerId && this.layerManager) {
|
||||
// 检查原活动图层是否还存在
|
||||
const originalLayer = this.layerManager.getLayerById(
|
||||
this.oldActiveLayerId
|
||||
);
|
||||
const originalLayer = this.layerManager.getLayerById(this.oldActiveLayerId);
|
||||
if (originalLayer) {
|
||||
this.layerManager.setActiveLayer(this.oldActiveLayerId);
|
||||
} else {
|
||||
@@ -603,9 +593,7 @@ export class CreateTextCommand extends Command {
|
||||
// 从子图层中移除
|
||||
if (positionInfo.parent.children && positionInfo.index >= 0) {
|
||||
positionInfo.parent.children.splice(positionInfo.index, 1);
|
||||
console.log(
|
||||
`已从子图层移除: ${this.layerId} (父图层: ${positionInfo.parent.name})`
|
||||
);
|
||||
console.log(`已从子图层移除: ${this.layerId} (父图层: ${positionInfo.parent.name})`);
|
||||
}
|
||||
} else {
|
||||
// 从一级图层中移除
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -43,11 +43,7 @@
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<button
|
||||
class="control-btn remove"
|
||||
@click="removeMemorizedSize"
|
||||
v-if="canRemoveSize"
|
||||
>
|
||||
<button class="control-btn remove" @click="removeMemorizedSize" v-if="canRemoveSize">
|
||||
-
|
||||
</button>
|
||||
</div>
|
||||
@@ -57,16 +53,9 @@
|
||||
|
||||
<!-- 1.这里加上过渡动画 颜色选择器 - 仅在特定工具下显示 -->
|
||||
<transition name="color-picker-fade" mode="out-in">
|
||||
<div
|
||||
v-if="showColorPicker"
|
||||
class="color-picker-container"
|
||||
key="color-picker"
|
||||
>
|
||||
<div v-if="showColorPicker" class="color-picker-container" key="color-picker">
|
||||
<label for="color-picker" class="current-color-label">
|
||||
<div
|
||||
class="current-color"
|
||||
:style="{ backgroundColor: brushColor }"
|
||||
></div>
|
||||
<div class="current-color" :style="{ backgroundColor: brushColor }"></div>
|
||||
</label>
|
||||
<input
|
||||
type="color"
|
||||
@@ -115,9 +104,7 @@
|
||||
></div>
|
||||
</div>
|
||||
<div class="tooltip-content">
|
||||
<div class="tooltip-text">
|
||||
{{ Math.round(brushOpacity * 100) }}%
|
||||
</div>
|
||||
<div class="tooltip-text">{{ Math.round(brushOpacity * 100) }}%</div>
|
||||
<div class="tooltip-controls">
|
||||
<button
|
||||
class="control-btn add"
|
||||
@@ -470,7 +457,9 @@ watch(
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 5px;
|
||||
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;
|
||||
backdrop-filter: blur(2px);
|
||||
color: #333;
|
||||
@@ -478,7 +467,9 @@ watch(
|
||||
-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;
|
||||
|
||||
transform: translate3d(0, -50%, 0); // 确保使用3D变换以提高性能
|
||||
@@ -492,8 +483,7 @@ watch(
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% /
|
||||
10px 10px;
|
||||
background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% / 10px 10px;
|
||||
border-radius: 6px;
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 5px;
|
||||
@@ -521,8 +511,7 @@ watch(
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% /
|
||||
10px 10px;
|
||||
background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% / 10px 10px;
|
||||
}
|
||||
|
||||
.opacity-color {
|
||||
@@ -617,13 +606,17 @@ watch(
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
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;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
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-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-leave-to {
|
||||
@@ -694,7 +689,9 @@ watch(
|
||||
// 颜色选择器过渡动画
|
||||
.color-picker-fade-enter-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-leave-to {
|
||||
@@ -705,7 +702,9 @@ watch(
|
||||
// 透明度滑块过渡动画
|
||||
.opacity-slider-fade-enter-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-leave-to {
|
||||
|
||||
@@ -12,25 +12,16 @@
|
||||
v-for="brush in brushStore.state.availableBrushes"
|
||||
:key="brush.id"
|
||||
@click="setBrushTypeWithCommand(brush.id)"
|
||||
:class="[
|
||||
'brush-type-item',
|
||||
{ active: brushStore.state.type === brush.id },
|
||||
]"
|
||||
:class="['brush-type-item', { active: brushStore.state.type === brush.id }]"
|
||||
>
|
||||
<div
|
||||
class="brush-preview"
|
||||
:style="getBrushPreviewStyle(brush)"
|
||||
></div>
|
||||
<div class="brush-preview" :style="getBrushPreviewStyle(brush)"></div>
|
||||
<span class="brush-name">{{ brush.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 动态渲染笔刷可配置属性 -->
|
||||
<template
|
||||
v-for="(properties, category) in propertiesByCategory"
|
||||
:key="category"
|
||||
>
|
||||
<template v-for="(properties, category) in propertiesByCategory" :key="category">
|
||||
<div class="brush-section">
|
||||
<div class="section-header">
|
||||
<span>{{ category }}</span>
|
||||
@@ -43,11 +34,7 @@
|
||||
|
||||
<!-- 针对每个属性,根据其类型渲染合适的控件 -->
|
||||
<div class="property-list">
|
||||
<div
|
||||
v-for="prop in properties"
|
||||
:key="prop.id"
|
||||
class="property-item"
|
||||
>
|
||||
<div v-for="prop in properties" :key="prop.id" class="property-item">
|
||||
<!-- 滑块控件 -->
|
||||
<template v-if="prop.type === 'slider'">
|
||||
<div class="slider-property">
|
||||
@@ -61,13 +48,7 @@
|
||||
<input
|
||||
type="range"
|
||||
:value="prop.value"
|
||||
@input="
|
||||
(e) =>
|
||||
handlePropertyChange(
|
||||
prop.id,
|
||||
parseFloat(e.target.value)
|
||||
)
|
||||
"
|
||||
@input="(e) => handlePropertyChange(prop.id, parseFloat(e.target.value))"
|
||||
:min="prop.min || 0"
|
||||
:max="prop.max || 100"
|
||||
:step="prop.step || 1"
|
||||
@@ -93,25 +74,19 @@
|
||||
<div class="color-property">
|
||||
<div class="color-header">
|
||||
<span>{{ prop.name }}</span>
|
||||
<div
|
||||
class="color-preview"
|
||||
:style="{ backgroundColor: prop.value }"
|
||||
></div>
|
||||
<div class="color-preview" :style="{ backgroundColor: prop.value }"></div>
|
||||
</div>
|
||||
<div class="color-row">
|
||||
<input
|
||||
type="color"
|
||||
:value="prop.value"
|
||||
@input="
|
||||
(e) => handlePropertyChange(prop.id, e.target.value)
|
||||
"
|
||||
@input="(e) => handlePropertyChange(prop.id, e.target.value)"
|
||||
class="color-picker"
|
||||
/>
|
||||
<!-- 如果是主颜色,显示最近使用的颜色 -->
|
||||
<div v-if="prop.id === 'color'" class="recent-colors">
|
||||
<div
|
||||
v-for="(color, index) in brushStore.state
|
||||
.recentColors"
|
||||
v-for="(color, index) in brushStore.state.recentColors"
|
||||
:key="index"
|
||||
class="color-item"
|
||||
:style="{ backgroundColor: color }"
|
||||
@@ -130,9 +105,7 @@
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="prop.value"
|
||||
@change="
|
||||
(e) => handlePropertyChange(prop.id, e.target.checked)
|
||||
"
|
||||
@change="(e) => handlePropertyChange(prop.id, e.target.checked)"
|
||||
:id="`toggle-${prop.id}`"
|
||||
/>
|
||||
<label :for="`toggle-${prop.id}`"></label>
|
||||
@@ -146,9 +119,7 @@
|
||||
<span>{{ prop.name }}</span>
|
||||
<select
|
||||
:value="prop.value"
|
||||
@change="
|
||||
(e) => handlePropertyChange(prop.id, e.target.value)
|
||||
"
|
||||
@change="(e) => handlePropertyChange(prop.id, e.target.value)"
|
||||
class="property-select"
|
||||
>
|
||||
<option
|
||||
@@ -225,10 +196,7 @@
|
||||
<!-- 上传的纹理缓存区域 -->
|
||||
|
||||
<!-- 自定义纹理上传按钮 -->
|
||||
<div
|
||||
class="texture-item upload-item"
|
||||
@click="triggerTextureUpload"
|
||||
>
|
||||
<div class="texture-item upload-item" @click="triggerTextureUpload">
|
||||
<div class="upload-icon">
|
||||
<span>+</span>
|
||||
</div>
|
||||
@@ -275,9 +243,7 @@
|
||||
<input
|
||||
type="text"
|
||||
:value="prop.value"
|
||||
@input="
|
||||
(e) => handlePropertyChange(prop.id, e.target.value)
|
||||
"
|
||||
@input="(e) => handlePropertyChange(prop.id, e.target.value)"
|
||||
class="property-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -360,13 +326,7 @@
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="brushStore.state.shadowEnabled"
|
||||
@change="
|
||||
(e) =>
|
||||
handleShadowPropertyChange(
|
||||
'shadowEnabled',
|
||||
e.target.checked
|
||||
)
|
||||
"
|
||||
@change="(e) => handleShadowPropertyChange('shadowEnabled', e.target.checked)"
|
||||
id="shadow-enabled"
|
||||
/>
|
||||
<label for="shadow-enabled"></label>
|
||||
@@ -390,13 +350,7 @@
|
||||
<input
|
||||
type="color"
|
||||
:value="brushStore.state.shadowColor"
|
||||
@input="
|
||||
(e) =>
|
||||
handleShadowPropertyChange(
|
||||
'shadowColor',
|
||||
e.target.value
|
||||
)
|
||||
"
|
||||
@input="(e) => handleShadowPropertyChange('shadowColor', e.target.value)"
|
||||
class="color-picker"
|
||||
/>
|
||||
</div>
|
||||
@@ -408,20 +362,14 @@
|
||||
<div class="slider-property">
|
||||
<div class="slider-header">
|
||||
<span>{{ $t("阴影宽度") }}</span>
|
||||
<span class="slider-value"
|
||||
>{{ brushStore.state.shadowWidth }}px</span
|
||||
>
|
||||
<span class="slider-value">{{ brushStore.state.shadowWidth }}px</span>
|
||||
</div>
|
||||
<div class="slider-container">
|
||||
<input
|
||||
type="range"
|
||||
:value="brushStore.state.shadowWidth"
|
||||
@input="
|
||||
(e) =>
|
||||
handleShadowPropertyChange(
|
||||
'shadowWidth',
|
||||
parseFloat(e.target.value)
|
||||
)
|
||||
(e) => handleShadowPropertyChange('shadowWidth', parseFloat(e.target.value))
|
||||
"
|
||||
:min="0"
|
||||
:max="50"
|
||||
@@ -435,8 +383,7 @@
|
||||
:key="preset"
|
||||
@click="handleShadowPropertyChange('shadowWidth', preset)"
|
||||
:class="{
|
||||
active:
|
||||
Math.abs(brushStore.state.shadowWidth - preset) < 0.1,
|
||||
active: Math.abs(brushStore.state.shadowWidth - preset) < 0.1,
|
||||
}"
|
||||
>
|
||||
{{ preset }}
|
||||
@@ -450,9 +397,7 @@
|
||||
<div class="slider-property">
|
||||
<div class="slider-header">
|
||||
<span>{{ $t("阴影X偏移") }}</span>
|
||||
<span class="slider-value"
|
||||
>{{ brushStore.state.shadowOffsetX }}px</span
|
||||
>
|
||||
<span class="slider-value">{{ brushStore.state.shadowOffsetX }}px</span>
|
||||
</div>
|
||||
<div class="slider-container">
|
||||
<input
|
||||
@@ -460,10 +405,7 @@
|
||||
:value="brushStore.state.shadowOffsetX"
|
||||
@input="
|
||||
(e) =>
|
||||
handleShadowPropertyChange(
|
||||
'shadowOffsetX',
|
||||
parseFloat(e.target.value)
|
||||
)
|
||||
handleShadowPropertyChange('shadowOffsetX', parseFloat(e.target.value))
|
||||
"
|
||||
:min="-50"
|
||||
:max="50"
|
||||
@@ -475,13 +417,9 @@
|
||||
<button
|
||||
v-for="preset in [-10, -5, 0, 5, 10]"
|
||||
:key="preset"
|
||||
@click="
|
||||
handleShadowPropertyChange('shadowOffsetX', preset)
|
||||
"
|
||||
@click="handleShadowPropertyChange('shadowOffsetX', preset)"
|
||||
:class="{
|
||||
active:
|
||||
Math.abs(brushStore.state.shadowOffsetX - preset) <
|
||||
0.1,
|
||||
active: Math.abs(brushStore.state.shadowOffsetX - preset) < 0.1,
|
||||
}"
|
||||
>
|
||||
{{ preset }}
|
||||
@@ -495,9 +433,7 @@
|
||||
<div class="slider-property">
|
||||
<div class="slider-header">
|
||||
<span>{{ $t("阴影Y偏移") }}</span>
|
||||
<span class="slider-value"
|
||||
>{{ brushStore.state.shadowOffsetY }}px</span
|
||||
>
|
||||
<span class="slider-value">{{ brushStore.state.shadowOffsetY }}px</span>
|
||||
</div>
|
||||
<div class="slider-container">
|
||||
<input
|
||||
@@ -505,10 +441,7 @@
|
||||
:value="brushStore.state.shadowOffsetY"
|
||||
@input="
|
||||
(e) =>
|
||||
handleShadowPropertyChange(
|
||||
'shadowOffsetY',
|
||||
parseFloat(e.target.value)
|
||||
)
|
||||
handleShadowPropertyChange('shadowOffsetY', parseFloat(e.target.value))
|
||||
"
|
||||
:min="-50"
|
||||
:max="50"
|
||||
@@ -520,13 +453,9 @@
|
||||
<button
|
||||
v-for="preset in [-10, -5, 0, 5, 10]"
|
||||
:key="preset"
|
||||
@click="
|
||||
handleShadowPropertyChange('shadowOffsetY', preset)
|
||||
"
|
||||
@click="handleShadowPropertyChange('shadowOffsetY', preset)"
|
||||
:class="{
|
||||
active:
|
||||
Math.abs(brushStore.state.shadowOffsetY - preset) <
|
||||
0.1,
|
||||
active: Math.abs(brushStore.state.shadowOffsetY - preset) < 0.1,
|
||||
}"
|
||||
>
|
||||
{{ preset }}
|
||||
@@ -544,14 +473,8 @@
|
||||
class="shadow-preview-element"
|
||||
:style="{
|
||||
backgroundColor: brushStore.state.color,
|
||||
width: `${Math.max(
|
||||
20,
|
||||
Math.min(60, brushStore.state.size)
|
||||
)}px`,
|
||||
height: `${Math.max(
|
||||
20,
|
||||
Math.min(60, brushStore.state.size)
|
||||
)}px`,
|
||||
width: `${Math.max(20, Math.min(60, brushStore.state.size))}px`,
|
||||
height: `${Math.max(20, Math.min(60, brushStore.state.size))}px`,
|
||||
boxShadow: brushStore.state.shadowEnabled
|
||||
? `${brushStore.state.shadowOffsetX}px ${brushStore.state.shadowOffsetY}px ${brushStore.state.shadowWidth}px ${brushStore.state.shadowColor}`
|
||||
: 'none',
|
||||
@@ -567,11 +490,7 @@
|
||||
<div class="brush-section">
|
||||
<div class="section-header">
|
||||
<span>笔刷预设</span>
|
||||
<button
|
||||
class="save-preset-btn"
|
||||
@click="saveCurrentAsPreset"
|
||||
title="保存当前设置为预设"
|
||||
>
|
||||
<button class="save-preset-btn" @click="saveCurrentAsPreset" title="保存当前设置为预设">
|
||||
<i class="save-icon">+</i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -694,9 +613,7 @@ const filteredTextures = computed(() => {
|
||||
if (selectedCategory.value === "全部") {
|
||||
return textureLibrary;
|
||||
}
|
||||
return textureLibrary.filter(
|
||||
(texture) => texture.category === selectedCategory.value
|
||||
);
|
||||
return textureLibrary.filter((texture) => texture.category === selectedCategory.value);
|
||||
});
|
||||
|
||||
// 从材质库选择材质
|
||||
@@ -909,8 +826,7 @@ function getBrushPreviewStyle(brush) {
|
||||
case "spray":
|
||||
return {
|
||||
...baseStyle,
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, currentColor 1px, transparent 1px)",
|
||||
backgroundImage: "radial-gradient(circle, currentColor 1px, transparent 1px)",
|
||||
backgroundSize: "4px 4px",
|
||||
backgroundPosition: "center",
|
||||
opacity: 0.8,
|
||||
@@ -931,8 +847,7 @@ function getBrushPreviewStyle(brush) {
|
||||
case "rainbow":
|
||||
return {
|
||||
...baseStyle,
|
||||
background:
|
||||
"linear-gradient(90deg, red, orange, yellow, green, blue, indigo, violet)",
|
||||
background: "linear-gradient(90deg, red, orange, yellow, green, blue, indigo, violet)",
|
||||
height: "3px",
|
||||
};
|
||||
case "texture":
|
||||
@@ -957,10 +872,7 @@ function applyPresetWithCommand(presetIndex) {
|
||||
// 保存当前设置为预设
|
||||
function saveCurrentAsPreset() {
|
||||
// 简单实现,可以后续优化为弹窗输入名称
|
||||
const name = prompt(
|
||||
"请输入预设名称:",
|
||||
`预设 ${BrushStore.state.presets.length + 1}`
|
||||
);
|
||||
const name = prompt("请输入预设名称:", `预设 ${BrushStore.state.presets.length + 1}`);
|
||||
if (name) {
|
||||
const presetIndex = BrushStore.saveCurrentAsPreset(name);
|
||||
// 应用新创建的预设(可选)
|
||||
@@ -1740,7 +1652,9 @@ const brushStore = BrushStore;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1930,12 +1844,7 @@ const brushStore = BrushStore;
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
rgba(0, 0, 0, 0.08),
|
||||
transparent
|
||||
);
|
||||
background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.08), transparent);
|
||||
}
|
||||
|
||||
.uploaded-textures-divider span {
|
||||
@@ -2022,8 +1931,7 @@ const brushStore = BrushStore;
|
||||
}
|
||||
|
||||
.shadow-preview-box {
|
||||
background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% /
|
||||
10px 10px;
|
||||
background: repeating-conic-gradient(#f5f5f5 0% 25%, #ffffff 0% 50%) 50% / 10px 10px;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
display: flex;
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
<script setup>
|
||||
import {
|
||||
inject,
|
||||
ref,
|
||||
provide,
|
||||
onMounted,
|
||||
computed,
|
||||
watch,
|
||||
onUnmounted,
|
||||
} from "vue";
|
||||
import { inject, ref, provide, onMounted, computed, watch, onUnmounted } from "vue";
|
||||
import { OperationType } from "../utils/layerHelper";
|
||||
import BrushPanel from "./BrushPanel.vue";
|
||||
import { BrushStore } from "../store/BrushStore";
|
||||
@@ -50,9 +42,7 @@ const lastColor = ref("#ffffff");
|
||||
// return props.activeTool === OperationType.DRAW;
|
||||
// });
|
||||
|
||||
function updateCanvasSize(
|
||||
{ width, height } = { width: props.width, height: props.height }
|
||||
) {
|
||||
function updateCanvasSize({ width, height } = { width: props.width, height: props.height }) {
|
||||
if (!layerManager) {
|
||||
console.warn("LayerManager 未初始化,无法调整背景层尺寸");
|
||||
return;
|
||||
@@ -366,11 +356,7 @@ onMounted(() => {
|
||||
|
||||
<!-- 绘图工具设置 -->
|
||||
<div class="canvas-settings gap-20" v-if="!props.enabledRedGreenMode">
|
||||
<div
|
||||
class="btn"
|
||||
:class="{ active: showBrushPanel }"
|
||||
@click="toggleBrushPanel"
|
||||
>
|
||||
<div class="btn" :class="{ active: showBrushPanel }" @click="toggleBrushPanel">
|
||||
<!-- <span class="setting-label">笔刷:</span>/ -->
|
||||
<div class="brush-selector">
|
||||
<SvgIcon name="CBrushTop" size="22"></SvgIcon>
|
||||
@@ -385,11 +371,7 @@ onMounted(() => {
|
||||
<!-- <span class="brush-dropdown">▼</span> -->
|
||||
</div>
|
||||
<!-- 笔刷面板 -->
|
||||
<div
|
||||
v-if="showBrushPanel"
|
||||
class="brush-panel-container"
|
||||
ref="brushPanelRef"
|
||||
>
|
||||
<div v-if="showBrushPanel" class="brush-panel-container" ref="brushPanelRef">
|
||||
<Teleport to="body">
|
||||
<BrushPanel />
|
||||
</Teleport>
|
||||
|
||||
@@ -16,9 +16,7 @@ onMounted(() => {
|
||||
platform.value = {
|
||||
isMac: keyboardManager.platform === "mac",
|
||||
isIOS: keyboardManager.platform === "ios",
|
||||
isIPad:
|
||||
keyboardManager.platform === "ios" &&
|
||||
/iPad/.test(window.navigator.userAgent),
|
||||
isIPad: keyboardManager.platform === "ios" && /iPad/.test(window.navigator.userAgent),
|
||||
isTouchDevice: keyboardManager.isTouchDevice,
|
||||
isWindows: keyboardManager.platform === "windows",
|
||||
isAndroid: keyboardManager.platform === "android",
|
||||
@@ -58,10 +56,10 @@ function convertShortcuts(managerShortcuts) {
|
||||
increaseBrushSize: "增加笔触大小",
|
||||
decreaseBrushSize: "减小笔触大小",
|
||||
toggleTempTool: "临时切换工具",
|
||||
newLayer: "新建图层",
|
||||
groupLayers: "组合图层",
|
||||
ungroupLayers: "取消组合",
|
||||
mergeLayers: "合并图层",
|
||||
// newLayer: "新建图层",
|
||||
// groupLayers: "组合图层",
|
||||
// ungroupLayers: "取消组合",
|
||||
// mergeLayers: "合并图层",
|
||||
};
|
||||
|
||||
// 工具ID到显示名称的映射
|
||||
@@ -69,11 +67,11 @@ function convertShortcuts(managerShortcuts) {
|
||||
select: "选择模式",
|
||||
draw: "绘画模式",
|
||||
eraser: "橡皮擦模式",
|
||||
eyedropper: "吸色工具",
|
||||
// eyedropper: "吸色工具",
|
||||
pan: "移动画布",
|
||||
lasso: "套索工具",
|
||||
area_custom: "自由选区工具",
|
||||
wave: "波浪工具",
|
||||
// area_custom: "自由选区工具",
|
||||
// wave: "波浪工具",
|
||||
liquify: "液化工具",
|
||||
};
|
||||
|
||||
@@ -82,11 +80,7 @@ function convertShortcuts(managerShortcuts) {
|
||||
let actionDisplay = actionDisplayMap[shortcut.action] || shortcut.action;
|
||||
|
||||
// 特殊处理工具选择
|
||||
if (
|
||||
shortcut.action === "selectTool" &&
|
||||
shortcut.param &&
|
||||
toolDisplayMap[shortcut.param]
|
||||
) {
|
||||
if (shortcut.action === "selectTool" && shortcut.param && toolDisplayMap[shortcut.param]) {
|
||||
actionDisplay = toolDisplayMap[shortcut.param];
|
||||
}
|
||||
|
||||
@@ -144,12 +138,12 @@ function generateDefaultShortcuts() {
|
||||
mac: "Delete 或 ⌫",
|
||||
touch: "长按选中元素后点击删除",
|
||||
},
|
||||
{
|
||||
action: "全选",
|
||||
windows: "Ctrl+A",
|
||||
mac: "⌘+A",
|
||||
touch: "无",
|
||||
},
|
||||
// {
|
||||
// action: "全选",
|
||||
// windows: "Ctrl+A",
|
||||
// mac: "⌘+A",
|
||||
// touch: "无",
|
||||
// },
|
||||
{
|
||||
action: "复制",
|
||||
windows: "Ctrl+C",
|
||||
@@ -198,12 +192,12 @@ function generateDefaultShortcuts() {
|
||||
mac: "E",
|
||||
touch: "点击橡皮擦工具",
|
||||
},
|
||||
{
|
||||
action: "吸色工具",
|
||||
windows: "I",
|
||||
mac: "I",
|
||||
touch: "点击吸色工具",
|
||||
},
|
||||
// {
|
||||
// action: "吸色工具",
|
||||
// windows: "I",
|
||||
// mac: "I",
|
||||
// touch: "点击吸色工具",
|
||||
// },
|
||||
{
|
||||
action: "增加笔触大小",
|
||||
windows: "]",
|
||||
@@ -251,39 +245,24 @@ function getShortcutForCurrentPlatform(shortcut) {
|
||||
// 按分类获取快捷键
|
||||
function getShortcutsByCategory(category) {
|
||||
const categoryMap = {
|
||||
basic: [
|
||||
"撤销",
|
||||
"重做",
|
||||
"全选",
|
||||
"复制",
|
||||
"粘贴",
|
||||
"剪切",
|
||||
"删除选中元素",
|
||||
"上传图片",
|
||||
],
|
||||
// basic: ["撤销", "重做", "全选", "复制", "粘贴", "剪切", "删除选中元素", "上传图片"],
|
||||
basic: ["撤销", "重做", "复制", "粘贴", "剪切", "删除选中元素", "上传图片"],
|
||||
view: ["缩放画布", "移动画布"],
|
||||
tools: [
|
||||
"绘画模式",
|
||||
"选择模式",
|
||||
"橡皮擦模式",
|
||||
"吸色工具",
|
||||
// "吸色工具",
|
||||
"套索工具",
|
||||
"自由选区工具",
|
||||
"波浪工具",
|
||||
// "自由选区工具",
|
||||
// "波浪工具",
|
||||
"液化工具",
|
||||
],
|
||||
brush: [
|
||||
"增加笔触大小",
|
||||
"减小笔触大小",
|
||||
"增加材质图片大小",
|
||||
"减小材质图片大小",
|
||||
],
|
||||
layer: ["新建图层", "组合图层", "取消组合", "合并图层"],
|
||||
brush: ["增加笔触大小", "减小笔触大小", "增加材质图片大小", "减小材质图片大小"],
|
||||
// layer: ["新建图层", "组合图层", "取消组合", "合并图层"],
|
||||
};
|
||||
|
||||
return shortcuts.value.filter((s) =>
|
||||
categoryMap[category]?.includes(s.action)
|
||||
);
|
||||
return shortcuts.value.filter((s) => categoryMap[category]?.includes(s.action));
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -313,10 +292,7 @@ function getShortcutsByCategory(category) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in getShortcutsByCategory('basic')"
|
||||
:key="item.action"
|
||||
>
|
||||
<tr v-for="item in getShortcutsByCategory('basic')" :key="item.action">
|
||||
<td>{{ item.action }}</td>
|
||||
<td>{{ getShortcutForCurrentPlatform(item) }}</td>
|
||||
</tr>
|
||||
@@ -334,10 +310,7 @@ function getShortcutsByCategory(category) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in getShortcutsByCategory('view')"
|
||||
:key="item.action"
|
||||
>
|
||||
<tr v-for="item in getShortcutsByCategory('view')" :key="item.action">
|
||||
<td>{{ item.action }}</td>
|
||||
<td>{{ getShortcutForCurrentPlatform(item) }}</td>
|
||||
</tr>
|
||||
@@ -355,10 +328,7 @@ function getShortcutsByCategory(category) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in getShortcutsByCategory('tools')"
|
||||
:key="item.action"
|
||||
>
|
||||
<tr v-for="item in getShortcutsByCategory('tools')" :key="item.action">
|
||||
<td>{{ item.action }}</td>
|
||||
<td>{{ getShortcutForCurrentPlatform(item) }}</td>
|
||||
</tr>
|
||||
@@ -376,10 +346,7 @@ function getShortcutsByCategory(category) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in getShortcutsByCategory('brush')"
|
||||
:key="item.action"
|
||||
>
|
||||
<tr v-for="item in getShortcutsByCategory('brush')" :key="item.action">
|
||||
<td>{{ item.action }}</td>
|
||||
<td>{{ getShortcutForCurrentPlatform(item) }}</td>
|
||||
</tr>
|
||||
@@ -387,7 +354,7 @@ function getShortcutsByCategory(category) {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-category">
|
||||
<!-- <div class="shortcuts-category">
|
||||
<h3>图层操作</h3>
|
||||
<table class="shortcuts-table">
|
||||
<thead>
|
||||
@@ -397,16 +364,13 @@ function getShortcutsByCategory(category) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in getShortcutsByCategory('layer')"
|
||||
:key="item.action"
|
||||
>
|
||||
<tr v-for="item in getShortcutsByCategory('layer')" :key="item.action">
|
||||
<td>{{ item.action }}</td>
|
||||
<td>{{ getShortcutForCurrentPlatform(item) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="touch-tips" v-if="platform.isTouchDevice">
|
||||
<h3>触控设备提示</h3>
|
||||
|
||||
@@ -145,11 +145,7 @@ const handleItemMouseEnter = (item, index, event) => {
|
||||
// 处理鼠标在菜单项内移动
|
||||
const handleItemMouseMove = (item, index, event) => {
|
||||
// 如果当前菜单项有子菜单但子菜单未显示,则显示子菜单
|
||||
if (
|
||||
item.children &&
|
||||
item.children.length > 0 &&
|
||||
hoveredItem.value !== index
|
||||
) {
|
||||
if (item.children && item.children.length > 0 && hoveredItem.value !== index) {
|
||||
const element = event.target.closest(".context-menu-item");
|
||||
showSubmenu(item, index, element);
|
||||
}
|
||||
@@ -261,10 +257,7 @@ onUnmounted(() => {
|
||||
>
|
||||
<template v-for="(item, index) in items" :key="index">
|
||||
<!-- 分隔线 -->
|
||||
<div
|
||||
v-if="item.type === 'divider'"
|
||||
class="context-menu-divider"
|
||||
></div>
|
||||
<div v-if="item.type === 'divider'" class="context-menu-divider"></div>
|
||||
|
||||
<!-- 菜单项 -->
|
||||
<div
|
||||
@@ -294,38 +287,24 @@ onUnmounted(() => {
|
||||
<span class="context-menu-shortcut" v-if="item.shortcut">
|
||||
{{ item.shortcut }}
|
||||
</span>
|
||||
<span
|
||||
class="context-menu-arrow"
|
||||
v-if="item.children && item.children.length > 0"
|
||||
>
|
||||
<span class="context-menu-arrow" v-if="item.children && item.children.length > 0">
|
||||
<SvgIcon name="CRight" size="12" />
|
||||
</span>
|
||||
|
||||
<!-- 子菜单 -->
|
||||
<transition name="context-submenu">
|
||||
<div
|
||||
v-if="
|
||||
item.children &&
|
||||
item.children.length > 0 &&
|
||||
hoveredItem === index
|
||||
"
|
||||
v-if="item.children && item.children.length > 0 && hoveredItem === index"
|
||||
class="context-submenu"
|
||||
:class="{
|
||||
'submenu-left':
|
||||
submenuPositions.get(index)?.direction === 'left',
|
||||
'submenu-left': submenuPositions.get(index)?.direction === 'left',
|
||||
}"
|
||||
@mouseenter="handleSubmenuMouseEnter(index)"
|
||||
@mouseleave="handleSubmenuMouseLeave"
|
||||
>
|
||||
<template
|
||||
v-for="(subItem, subIndex) in item.children"
|
||||
:key="subIndex"
|
||||
>
|
||||
<template v-for="(subItem, subIndex) in item.children" :key="subIndex">
|
||||
<!-- 子菜单分隔线 -->
|
||||
<div
|
||||
v-if="subItem.type === 'divider'"
|
||||
class="context-menu-divider"
|
||||
></div>
|
||||
<div v-if="subItem.type === 'divider'" class="context-menu-divider"></div>
|
||||
|
||||
<!-- 子菜单项 -->
|
||||
<div
|
||||
@@ -342,9 +321,7 @@ onUnmounted(() => {
|
||||
:name="subItem.icon"
|
||||
size="14"
|
||||
:style="{
|
||||
transform: subItem.inverIcon
|
||||
? `rotate(90deg)`
|
||||
: 'none',
|
||||
transform: subItem.inverIcon ? `rotate(90deg)` : 'none',
|
||||
}"
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -250,13 +250,7 @@ function handleTouchEnd(event) {
|
||||
|
||||
function handleUpdateChildLayers(newChildren) {
|
||||
// 更新当前组图层的children数组
|
||||
console.log(
|
||||
"更新子图层顺序:",
|
||||
"父图层ID:",
|
||||
props.layer.id,
|
||||
"新顺序:",
|
||||
newChildren
|
||||
);
|
||||
console.log("更新子图层顺序:", "父图层ID:", 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('拖拽排序')">
|
||||
<SvgIcon
|
||||
v-if="!isHidenDragHandle"
|
||||
:name="isChild ? 'CSort' : 'CSort'"
|
||||
:size="32"
|
||||
></SvgIcon>
|
||||
<SvgIcon v-if="!isHidenDragHandle" :name="isChild ? 'CSort' : 'CSort'" :size="32"></SvgIcon>
|
||||
</div>
|
||||
|
||||
<!-- 图层头部 -->
|
||||
<div class="layer-header">
|
||||
<!-- 多选复选框 -->
|
||||
<div
|
||||
v-if="isMultiSelectMode && !isChild"
|
||||
class="layer-checkbox"
|
||||
@click.stop
|
||||
>
|
||||
<div v-if="isMultiSelectMode && !isChild" class="layer-checkbox" @click.stop>
|
||||
<Checkbox :checked="isSelected" @change="handleCheckboxChange" />
|
||||
</div>
|
||||
|
||||
@@ -453,12 +439,7 @@ function findParentLayerId() {
|
||||
>
|
||||
<SvgIcon name="CLock" :size="18"></SvgIcon>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="status-icon"
|
||||
:title="$t('未锁定')"
|
||||
@click.stop="handleToggleLock"
|
||||
>
|
||||
<span v-else class="status-icon" :title="$t('未锁定')" @click.stop="handleToggleLock">
|
||||
<SvgIcon name="CUnLock" :size="18"></SvgIcon>
|
||||
</span>
|
||||
|
||||
|
||||
@@ -96,8 +96,7 @@ const handleRootLayersSort = (event) => {
|
||||
|
||||
// 获取被拖拽的图层ID
|
||||
const draggedLayerId =
|
||||
item.getAttribute("data-layer-id") ||
|
||||
props.sortableRootLayers[oldIndex]?.id;
|
||||
item.getAttribute("data-layer-id") || props.sortableRootLayers[oldIndex]?.id;
|
||||
|
||||
if (!draggedLayerId) {
|
||||
console.error("❌ 无法获取被拖拽的图层ID");
|
||||
@@ -150,25 +149,14 @@ const handleRootLayersSort = (event) => {
|
||||
if (props.isChild) {
|
||||
// 子图层事件处理
|
||||
// 确保排序只影响当前组图层的children,而不是全局layers
|
||||
emit(
|
||||
"child-layers-sort",
|
||||
event,
|
||||
props.sortableRootLayers,
|
||||
props.parentLayerId
|
||||
);
|
||||
emit("child-layers-sort", event, props.sortableRootLayers, props.parentLayerId);
|
||||
} else {
|
||||
emit("root-layers-sort", event);
|
||||
}
|
||||
};
|
||||
|
||||
// 验证跨层级移动的有效性
|
||||
const validateCrossLevelMove = (
|
||||
layerId,
|
||||
fromType,
|
||||
toType,
|
||||
fromParentId,
|
||||
toParentId
|
||||
) => {
|
||||
const validateCrossLevelMove = (layerId, fromType, toType, fromParentId, toParentId) => {
|
||||
// 查找图层
|
||||
const layer = findLayerInHierarchy(layerId, fromType, fromParentId);
|
||||
|
||||
@@ -250,9 +238,7 @@ const handleDragRemove = (event) => {
|
||||
const canDeleteComputed = computed(() => {
|
||||
// 如果是子图层,检查父图层是否可以删除
|
||||
if (props.isChild) {
|
||||
const parentLayer = props.layers.find(
|
||||
(layer) => layer.id === props.parentLayerId
|
||||
);
|
||||
const parentLayer = props.layers.find((layer) => layer.id === props.parentLayerId);
|
||||
return parentLayer?.children?.length > 1;
|
||||
}
|
||||
// 否则直接返回根图层的可删除状态
|
||||
@@ -305,23 +291,14 @@ const canDeleteComputed = computed(() => {
|
||||
:is-editing="editingLayerId === layer.id"
|
||||
:editing-name="editingLayerName"
|
||||
:can-delete="
|
||||
canDeleteComputed &&
|
||||
!layer.isBackground &&
|
||||
!layer.isFixed &&
|
||||
!layer.locked
|
||||
canDeleteComputed && !layer.isBackground && !layer.isFixed && !layer.locked
|
||||
"
|
||||
:expanded-group-ids="expandedGroupIds"
|
||||
@click="(...args) => forwardEvent('layer-click', ...args)"
|
||||
@double-click="
|
||||
(...args) => forwardEvent('layer-double-click', ...args)
|
||||
"
|
||||
@double-click="(...args) => forwardEvent('layer-double-click', ...args)"
|
||||
@context-menu="(...args) => forwardEvent('context-menu', ...args)"
|
||||
@checkbox-change="
|
||||
(...args) => forwardEvent('checkbox-change', ...args)
|
||||
"
|
||||
@toggle-visibility="
|
||||
(...args) => forwardEvent('toggle-visibility', ...args)
|
||||
"
|
||||
@checkbox-change="(...args) => forwardEvent('checkbox-change', ...args)"
|
||||
@toggle-visibility="(...args) => forwardEvent('toggle-visibility', ...args)"
|
||||
@toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)"
|
||||
@delete="(...args) => forwardEvent('delete', ...args)"
|
||||
@edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)"
|
||||
@@ -330,18 +307,10 @@ const canDeleteComputed = computed(() => {
|
||||
@touch-start="(...args) => forwardEvent('touch-start', ...args)"
|
||||
@touch-move="(...args) => forwardEvent('touch-move', ...args)"
|
||||
@touch-end="(...args) => forwardEvent('touch-end', ...args)"
|
||||
@update:editing-name="
|
||||
(...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-child-lock="
|
||||
(...args) => forwardEvent('toggle-child-lock', ...args)
|
||||
"
|
||||
@update:editing-name="(...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-child-lock="(...args) => forwardEvent('toggle-child-lock', ...args)"
|
||||
@delete-child="(...args) => forwardEvent('delete-child', ...args)"
|
||||
@rename-child="(...args) => forwardEvent('rename-child', ...args)"
|
||||
/>
|
||||
@@ -370,16 +339,10 @@ const canDeleteComputed = computed(() => {
|
||||
:parentLayerId="layer.id"
|
||||
:group-name="groupName"
|
||||
@layer-click="(...args) => forwardEvent('layer-click', ...args)"
|
||||
@layer-double-click="
|
||||
(...args) => forwardEvent('layer-double-click', ...args)
|
||||
"
|
||||
@layer-double-click="(...args) => forwardEvent('layer-double-click', ...args)"
|
||||
@context-menu="(...args) => forwardEvent('context-menu', ...args)"
|
||||
@checkbox-change="
|
||||
(...args) => forwardEvent('checkbox-change', ...args)
|
||||
"
|
||||
@toggle-visibility="
|
||||
(...args) => forwardEvent('toggle-visibility', ...args)
|
||||
"
|
||||
@checkbox-change="(...args) => forwardEvent('checkbox-change', ...args)"
|
||||
@toggle-visibility="(...args) => forwardEvent('toggle-visibility', ...args)"
|
||||
@toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)"
|
||||
@delete="(...args) => forwardEvent('delete', ...args)"
|
||||
@edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)"
|
||||
@@ -388,33 +351,17 @@ const canDeleteComputed = computed(() => {
|
||||
@touch-start="(...args) => forwardEvent('touch-start', ...args)"
|
||||
@touch-move="(...args) => forwardEvent('touch-move', ...args)"
|
||||
@touch-end="(...args) => forwardEvent('touch-end', ...args)"
|
||||
@update:editing-name="
|
||||
(...args) => forwardEvent('update:editing-name', ...args)
|
||||
"
|
||||
@root-layers-sort="
|
||||
(...args) => forwardEvent('root-layers-sort', ...args)
|
||||
"
|
||||
@child-layers-sort="
|
||||
(...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)
|
||||
"
|
||||
@update:editing-name="(...args) => forwardEvent('update:editing-name', ...args)"
|
||||
@root-layers-sort="(...args) => forwardEvent('root-layers-sort', ...args)"
|
||||
@child-layers-sort="(...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="
|
||||
(...args) => forwardEvent('toggle-child-visibility', ...args)
|
||||
"
|
||||
@toggle-child-lock="
|
||||
(...args) => forwardEvent('toggle-child-lock', ...args)
|
||||
"
|
||||
@toggle-child-lock="(...args) => forwardEvent('toggle-child-lock', ...args)"
|
||||
@finish-child-layer-edit="
|
||||
(...args) => forwardEvent('finish-child-layer-edit', ...args)
|
||||
"
|
||||
@@ -424,9 +371,7 @@ const canDeleteComputed = computed(() => {
|
||||
@child-layer-edit-keydown="
|
||||
(...args) => forwardEvent('child-layer-edit-keydown', ...args)
|
||||
"
|
||||
@toggle-group-expanded="
|
||||
(...args) => forwardEvent('toggle-group-expanded', ...args)
|
||||
"
|
||||
@toggle-group-expanded="(...args) => forwardEvent('toggle-group-expanded', ...args)"
|
||||
@delete-child="(...args) => forwardEvent('delete-child', ...args)"
|
||||
@rename-child="(...args) => forwardEvent('rename-child', ...args)"
|
||||
/>
|
||||
@@ -517,7 +462,9 @@ const canDeleteComputed = computed(() => {
|
||||
// 跨层级拖拽目标区域高亮
|
||||
.sortable-layers {
|
||||
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;
|
||||
min-height: 40px; // 确保空组也有足够的拖拽区域
|
||||
|
||||
|
||||
@@ -72,26 +72,21 @@ const contextMenuItems = ref([]);
|
||||
// 计算属性:可排序的根级图层(排除背景层和固定层)
|
||||
const sortableRootLayers = computed(() => {
|
||||
if (!layers) return [];
|
||||
return layers.value.filter(
|
||||
(layer) => !layer.parentId && !layer.isFixed && !layer.isBackground
|
||||
);
|
||||
return layers.value.filter((layer) => !layer.parentId && !layer.isFixed && !layer.isBackground);
|
||||
});
|
||||
|
||||
// 计算属性:不可排序的固定图层(背景层和固定层)
|
||||
const fixedLayers = computed(() => {
|
||||
if (!layers) return [];
|
||||
return layers.value.filter((layer) => {
|
||||
if (props.showFixedLayer)
|
||||
return !layer.parentId && (layer.isFixed || layer.isBackground);
|
||||
if (props.showFixedLayer) return !layer.parentId && (layer.isFixed || layer.isBackground);
|
||||
return !layer.parentId && layer.isBackground; // 只显示背景层,不显示固定层 - 固定层用来做红绿图模式 和 放模特
|
||||
});
|
||||
});
|
||||
|
||||
// 计算属性:获取当前选中的图层
|
||||
const selectedLayers = computed(() => {
|
||||
return sortableRootLayers.value.filter((layer) =>
|
||||
selectedLayerIds.value.includes(layer.id)
|
||||
);
|
||||
return sortableRootLayers.value.filter((layer) => selectedLayerIds.value.includes(layer.id));
|
||||
});
|
||||
|
||||
// 计算属性:获取当前是否激活子图层
|
||||
@@ -224,9 +219,7 @@ function toggleLayerSelection(layer, event) {
|
||||
}
|
||||
|
||||
const isShift = event.shiftKey;
|
||||
const layerIndex = sortableRootLayers.value.findIndex(
|
||||
(l) => l.id === layer.id
|
||||
);
|
||||
const layerIndex = sortableRootLayers.value.findIndex((l) => l.id === layer.id);
|
||||
console.log("isShift", isShift);
|
||||
|
||||
if (isShift && lastSelectedIndex.value !== -1) {
|
||||
@@ -386,9 +379,7 @@ async function ungroupSelectedLayer() {
|
||||
|
||||
try {
|
||||
const childLayerIds = await layerManager?.ungroupLayers(groupLayer.id);
|
||||
console.log(
|
||||
`✅ 成功解组图层组: ${groupLayer.name}, 子图层: ${childLayerIds}`
|
||||
);
|
||||
console.log(`✅ 成功解组图层组: ${groupLayer.name}, 子图层: ${childLayerIds}`);
|
||||
|
||||
// 清除选择状态
|
||||
clearSelection();
|
||||
@@ -406,9 +397,7 @@ function deleteSelectedLayers() {
|
||||
}
|
||||
|
||||
// 检查是否包含不能删除的图层
|
||||
const undeletableLayers = selectedLayers.filter(
|
||||
(layer) => layer.isBackground || layer.isFixed
|
||||
);
|
||||
const undeletableLayers = selectedLayers.filter((layer) => layer.isBackground || layer.isFixed);
|
||||
|
||||
if (undeletableLayers.length > 0) {
|
||||
console.warn("选择的图层中包含背景层或固定层,无法删除");
|
||||
@@ -417,10 +406,7 @@ function deleteSelectedLayers() {
|
||||
|
||||
// 检查删除后是否还有足够的普通图层
|
||||
const remainingNormalLayers = layers.value.filter(
|
||||
(layer) =>
|
||||
!layer.isBackground &&
|
||||
!layer.isFixed &&
|
||||
!selectedLayerIds.value.includes(layer.id)
|
||||
(layer) => !layer.isBackground && !layer.isFixed && !selectedLayerIds.value.includes(layer.id)
|
||||
).length;
|
||||
|
||||
if (remainingNormalLayers < 1) {
|
||||
@@ -460,9 +446,7 @@ function startEditing(layer) {
|
||||
|
||||
// 下一帧聚焦输入框并选中文本
|
||||
nextTick(() => {
|
||||
const inputElement = document.querySelector(
|
||||
`input[data-layer-id="${layer.id}"]`
|
||||
);
|
||||
const inputElement = document.querySelector(`input[data-layer-id="${layer.id}"]`);
|
||||
if (inputElement) {
|
||||
inputElement.focus();
|
||||
inputElement.select();
|
||||
@@ -494,41 +478,41 @@ function handleEditKeydown(event) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取图层缩略图URL
|
||||
function getLayerThumbnail(layerId) {
|
||||
if (props.thumbnailManager) {
|
||||
return props.thumbnailManager.getLayerThumbnail(layerId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// 获取图层缩略图URL - 弃用
|
||||
// function getLayerThumbnail(layerId) {
|
||||
// if (props.thumbnailManager) {
|
||||
// return props.thumbnailManager.getLayerThumbnail(layerId);
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// 获取图层类型图标
|
||||
function getLayerTypeIcon(layer) {
|
||||
if (!layer) return "🖼️";
|
||||
// function getLayerTypeIcon(layer) {
|
||||
// if (!layer) return "🖼️";
|
||||
|
||||
if (isGroupLayer(layer)) {
|
||||
return "📁";
|
||||
}
|
||||
// if (isGroupLayer(layer)) {
|
||||
// return "📁";
|
||||
// }
|
||||
|
||||
if (layer.fabricObject) {
|
||||
switch (layer.fabricObject.type) {
|
||||
case "image":
|
||||
return "🖼️";
|
||||
case "text":
|
||||
return "📝";
|
||||
case "rect":
|
||||
return "▢";
|
||||
case "circle":
|
||||
return "⬤";
|
||||
case "path":
|
||||
return "✎";
|
||||
default:
|
||||
return "⬤";
|
||||
}
|
||||
}
|
||||
// if (layer.fabricObject) {
|
||||
// switch (layer.fabricObject.type) {
|
||||
// case "image":
|
||||
// return "🖼️";
|
||||
// case "text":
|
||||
// return "📝";
|
||||
// case "rect":
|
||||
// return "▢";
|
||||
// case "circle":
|
||||
// return "⬤";
|
||||
// case "path":
|
||||
// return "✎";
|
||||
// default:
|
||||
// return "⬤";
|
||||
// }
|
||||
// }
|
||||
|
||||
return "🖼️";
|
||||
}
|
||||
// return "🖼️";
|
||||
// }
|
||||
|
||||
// 获取图层的子图层
|
||||
function getChildLayers(parentId) {
|
||||
@@ -557,22 +541,22 @@ const toggleGroupExpanded = (groupId) => {
|
||||
expandedGroupIds.value = new Set(expandedGroupIds.value);
|
||||
};
|
||||
|
||||
// 渲染单个图层项(递归组件)
|
||||
function renderLayerItem(layer, index) {
|
||||
if (!layer) return null;
|
||||
// // 渲染单个图层项(递归组件)
|
||||
// function renderLayerItem(layer, index) {
|
||||
// if (!layer) return null;
|
||||
|
||||
const isGroup = isGroupLayerType(layer);
|
||||
const children = isGroup ? getChildLayers(layer.id) : [];
|
||||
// const isGroup = isGroupLayerType(layer);
|
||||
// const children = isGroup ? getChildLayers(layer.id) : [];
|
||||
|
||||
return {
|
||||
id: layer.id,
|
||||
name: layer.name,
|
||||
isGroup: isGroup,
|
||||
children: children,
|
||||
fabricObject: layer.fabricObject,
|
||||
visible: layer.visible,
|
||||
};
|
||||
}
|
||||
// return {
|
||||
// id: layer.id,
|
||||
// name: layer.name,
|
||||
// isGroup: isGroup,
|
||||
// children: children,
|
||||
// fabricObject: layer.fabricObject,
|
||||
// visible: layer.visible,
|
||||
// };
|
||||
// }
|
||||
|
||||
// 处理图层点击事件
|
||||
function handleLayerClick(layer, event) {
|
||||
@@ -580,12 +564,7 @@ function handleLayerClick(layer, event) {
|
||||
event.stopPropagation();
|
||||
|
||||
// 如果按住修饰键,执行多选逻辑
|
||||
if (
|
||||
event.ctrlKey ||
|
||||
event.metaKey ||
|
||||
event.shiftKey ||
|
||||
isMultiSelectMode.value
|
||||
) {
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey || isMultiSelectMode.value) {
|
||||
toggleLayerSelection(layer, event);
|
||||
} else {
|
||||
// 普通点击:进入单选模式
|
||||
@@ -595,11 +574,7 @@ function handleLayerClick(layer, event) {
|
||||
// 如果不是多选模式,才可激活图层
|
||||
// 1.如果是组,则设置组下的第一个子图层为活动图层
|
||||
// 2.否则直接设置活动图层
|
||||
if (
|
||||
isGroupLayerType(layer) &&
|
||||
layer.children &&
|
||||
layer.children.length > 0
|
||||
) {
|
||||
if (isGroupLayerType(layer) && layer.children && layer.children.length > 0) {
|
||||
// 如果是组图层,设置第一个子图层为活动图层
|
||||
layerManager?.setAllActiveGroupLayerCanvasObject?.(layer);
|
||||
setActiveLayer(layer.children[0].id, { parentId: layer.id });
|
||||
@@ -609,9 +584,7 @@ function handleLayerClick(layer, event) {
|
||||
layerManager?.updateLayersObjectsInteractivity();
|
||||
}
|
||||
}
|
||||
lastSelectedIndex.value = sortableRootLayers.value.findIndex(
|
||||
(l) => l.id === layer.id
|
||||
);
|
||||
lastSelectedIndex.value = sortableRootLayers.value.findIndex((l) => l.id === layer.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -675,10 +648,7 @@ function buildContextMenuItems(layer) {
|
||||
{
|
||||
label: "栅格化图层",
|
||||
icon: "CPicture",
|
||||
disabled:
|
||||
layer.isBackground ||
|
||||
layer.isFixed ||
|
||||
!layerManager?.canRasterizeLayer?.(layer.id),
|
||||
disabled: layer.isBackground || layer.isFixed || !layerManager?.canRasterizeLayer?.(layer.id),
|
||||
action: () => {
|
||||
rasterizeLayer(layer.id);
|
||||
hideContextMenu();
|
||||
@@ -688,10 +658,7 @@ function buildContextMenuItems(layer) {
|
||||
{
|
||||
label: "导出图层",
|
||||
icon: "CExport",
|
||||
disabled:
|
||||
layer.isBackground ||
|
||||
layer.isFixed ||
|
||||
!layerManager?.canRasterizeLayer?.(layer.id),
|
||||
disabled: layer.isBackground || layer.isFixed || !layerManager?.canRasterizeLayer?.(layer.id),
|
||||
action: () => {
|
||||
exportLayerToImage(layer.id);
|
||||
hideContextMenu();
|
||||
@@ -755,10 +722,7 @@ function buildContextMenuItems(layer) {
|
||||
label: "置顶",
|
||||
icon: "CBottom",
|
||||
inverIcon: true, // 倒置图标
|
||||
disabled:
|
||||
layer.isBackground ||
|
||||
layer.isFixed ||
|
||||
!layerManager?.canMoveToTop?.(layer.id),
|
||||
disabled: layer.isBackground || layer.isFixed || !layerManager?.canMoveToTop?.(layer.id),
|
||||
action: () => {
|
||||
moveLayerToTop(layer.id);
|
||||
hideContextMenu();
|
||||
@@ -767,10 +731,7 @@ function buildContextMenuItems(layer) {
|
||||
{
|
||||
label: "向上移动",
|
||||
icon: "CUp",
|
||||
disabled:
|
||||
layer.isBackground ||
|
||||
layer.isFixed ||
|
||||
!layerManager?.canMoveToTop?.(layer.id),
|
||||
disabled: layer.isBackground || layer.isFixed || !layerManager?.canMoveToTop?.(layer.id),
|
||||
action: () => {
|
||||
moveLayerUp(layer.id);
|
||||
hideContextMenu();
|
||||
@@ -780,9 +741,7 @@ function buildContextMenuItems(layer) {
|
||||
label: "向下移动",
|
||||
icon: "CDown",
|
||||
disabled:
|
||||
layer.isBackground ||
|
||||
layer.isFixed ||
|
||||
!layerManager?.canMoveToBottom?.(layer.id),
|
||||
layer.isBackground || layer.isFixed || !layerManager?.canMoveToBottom?.(layer.id),
|
||||
action: () => {
|
||||
moveLayerDown(layer.id);
|
||||
hideContextMenu();
|
||||
@@ -792,9 +751,7 @@ function buildContextMenuItems(layer) {
|
||||
label: "置底",
|
||||
icon: "CBottom",
|
||||
disabled:
|
||||
layer.isBackground ||
|
||||
layer.isFixed ||
|
||||
!layerManager?.canMoveToBottom?.(layer.id),
|
||||
layer.isBackground || layer.isFixed || !layerManager?.canMoveToBottom?.(layer.id),
|
||||
action: () => {
|
||||
moveLayerToBottom(layer.id);
|
||||
hideContextMenu();
|
||||
@@ -856,18 +813,13 @@ function canDeleteLayers() {
|
||||
if (selectedLayers.length === 0) return false;
|
||||
|
||||
// 检查是否包含不能删除的图层
|
||||
const undeletableLayers = selectedLayers.filter(
|
||||
(layer) => layer.isBackground || layer.isFixed
|
||||
);
|
||||
const undeletableLayers = selectedLayers.filter((layer) => layer.isBackground || layer.isFixed);
|
||||
|
||||
if (undeletableLayers.length > 0) return false;
|
||||
|
||||
// 检查删除后是否还有足够的普通图层
|
||||
const remainingNormalLayers = layers.value.filter(
|
||||
(layer) =>
|
||||
!layer.isBackground &&
|
||||
!layer.isFixed &&
|
||||
!selectedLayerIds.value.includes(layer.id)
|
||||
(layer) => !layer.isBackground && !layer.isFixed && !selectedLayerIds.value.includes(layer.id)
|
||||
).length;
|
||||
|
||||
return remainingNormalLayers >= 1;
|
||||
@@ -904,9 +856,7 @@ function startChildLayerEdit(childLayer) {
|
||||
childLayer.tempName = childLayer.name;
|
||||
|
||||
nextTick(() => {
|
||||
const inputElement = document.querySelector(
|
||||
`input[data-child-layer-id="${childLayer.id}"]`
|
||||
);
|
||||
const inputElement = document.querySelector(`input[data-child-layer-id="${childLayer.id}"]`);
|
||||
if (inputElement) {
|
||||
inputElement.focus();
|
||||
inputElement.select();
|
||||
@@ -943,8 +893,7 @@ function handleChildLayerEditKeydown(event, childLayer) {
|
||||
|
||||
// 选中子图层
|
||||
function selectChildLayer(layerId, parentId) {
|
||||
if (!isMultiSelectMode.value)
|
||||
layerManager?.setActiveLayer(layerId, { parentId });
|
||||
if (!isMultiSelectMode.value) layerManager?.setActiveLayer(layerId, { parentId });
|
||||
}
|
||||
|
||||
// 子图层右键菜单处理
|
||||
@@ -991,8 +940,7 @@ function buildChildLayerContextMenuItems(childLayer) {
|
||||
{
|
||||
label: childLayer.visible ? "隐藏图层" : "显示图层",
|
||||
icon: childLayer.visible ? "CUnEye" : "CEye",
|
||||
action: () =>
|
||||
toggleChildLayerVisibility(childLayer.id, childLayer.parentId),
|
||||
action: () => toggleChildLayerVisibility(childLayer.id, childLayer.parentId),
|
||||
},
|
||||
{ type: "divider" },
|
||||
// 移出组
|
||||
@@ -1045,9 +993,7 @@ async function handleRootLayersSort(event) {
|
||||
|
||||
try {
|
||||
layerManager?.reorderLayers(oldIndex, newIndex, layerId);
|
||||
console.log(
|
||||
`✅ 图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`
|
||||
);
|
||||
console.log(`✅ 图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`);
|
||||
} catch (error) {
|
||||
console.error("❌ 图层排序命令执行失败:", error);
|
||||
emit("reorder-layers", {
|
||||
@@ -1085,12 +1031,7 @@ async function handleChildLayersSort(event, childrenLayers, parentId) {
|
||||
const layerToMove = childrenLayers[oldIndex];
|
||||
|
||||
if (!layerToMove) {
|
||||
console.error(
|
||||
"❌ 找不到要移动的子图层,oldIndex:",
|
||||
oldIndex,
|
||||
"childLayers:",
|
||||
childrenLayers
|
||||
);
|
||||
console.error("❌ 找不到要移动的子图层,oldIndex:", oldIndex, "childLayers:", childrenLayers);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1105,9 +1046,7 @@ async function handleChildLayersSort(event, childrenLayers, parentId) {
|
||||
|
||||
try {
|
||||
layerManager?.moveLayerToIndex({ parentId, oldIndex, newIndex, layerId });
|
||||
console.log(
|
||||
`✅ 子图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`
|
||||
);
|
||||
console.log(`✅ 子图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`);
|
||||
} catch (error) {
|
||||
console.error("❌ 子图层排序命令执行失败:", error);
|
||||
emit("reorder-child-layers", {
|
||||
@@ -1168,11 +1107,7 @@ async function mergeGroupLayer(groupId) {
|
||||
const childLayerId = await layerManager.mergeGroupLayers(groupId);
|
||||
if (childLayerId) {
|
||||
const groupLayer = layers.value.find((l) => l.id === groupId);
|
||||
console.log(
|
||||
`✅ 成功合并组图层: ${
|
||||
groupLayer?.name || groupId
|
||||
}, 生成 ${childLayerId} 图层`
|
||||
);
|
||||
console.log(`✅ 成功合并组图层: ${groupLayer?.name || groupId}, 生成 ${childLayerId} 图层`);
|
||||
} else {
|
||||
console.warn("合并组图层失败");
|
||||
}
|
||||
@@ -1284,8 +1219,7 @@ async function handleCrossLevelMove(moveData) {
|
||||
目标图层不是组图层: "只能将图层移动到组图层中",
|
||||
};
|
||||
|
||||
const userMessage =
|
||||
errorMessages[error.message] || `移动失败: ${error.message}`;
|
||||
const userMessage = errorMessages[error.message] || `移动失败: ${error.message}`;
|
||||
|
||||
// 这里可以触发一个全局的错误提示组件
|
||||
// 暂时使用console.warn,实际项目中应该替换为适当的提示方式
|
||||
@@ -1315,9 +1249,7 @@ async function executeDirectMove(moveData) {
|
||||
} else if (fromContainerType === "child" && fromParentId) {
|
||||
sourceParent = layers.value.find((layer) => layer.id === fromParentId);
|
||||
if (sourceParent && sourceParent.children) {
|
||||
draggedLayer = sourceParent.children.find(
|
||||
(child) => child.id === layerId
|
||||
);
|
||||
draggedLayer = sourceParent.children.find((child) => child.id === layerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1385,9 +1317,7 @@ async function moveRootToGroup(draggedLayer, toParentId, newIndex) {
|
||||
}
|
||||
|
||||
// 从顶级图层数组中移除
|
||||
const rootIndex = layers.value.findIndex(
|
||||
(layer) => layer.id === draggedLayer.id
|
||||
);
|
||||
const rootIndex = layers.value.findIndex((layer) => layer.id === draggedLayer.id);
|
||||
if (rootIndex !== -1) {
|
||||
layers.value.splice(rootIndex, 1);
|
||||
}
|
||||
@@ -1432,9 +1362,7 @@ async function moveGroupToRoot(draggedLayer, fromParentId, newIndex) {
|
||||
} else {
|
||||
// 回退方案:手动更新图层关系
|
||||
// 从源父图层的children中移除
|
||||
const childIndex = sourceParent.children.findIndex(
|
||||
(child) => child.id === draggedLayer.id
|
||||
);
|
||||
const childIndex = sourceParent.children.findIndex((child) => child.id === draggedLayer.id);
|
||||
if (childIndex !== -1) {
|
||||
sourceParent.children.splice(childIndex, 1);
|
||||
}
|
||||
@@ -1461,12 +1389,7 @@ async function moveGroupToRoot(draggedLayer, fromParentId, newIndex) {
|
||||
}
|
||||
|
||||
// 在不同组之间移动
|
||||
async function moveGroupToGroup(
|
||||
draggedLayer,
|
||||
fromParentId,
|
||||
toParentId,
|
||||
newIndex
|
||||
) {
|
||||
async function moveGroupToGroup(draggedLayer, fromParentId, toParentId, newIndex) {
|
||||
console.log("🔄 在不同组间移动:", {
|
||||
layerId: draggedLayer.id,
|
||||
fromParentId,
|
||||
@@ -1488,18 +1411,11 @@ async function moveGroupToGroup(
|
||||
|
||||
// 使用 layerManager 的方法在组间移动
|
||||
if (layerManager?.moveLayerBetweenGroups) {
|
||||
await layerManager.moveLayerBetweenGroups(
|
||||
draggedLayer.id,
|
||||
fromParentId,
|
||||
toParentId,
|
||||
newIndex
|
||||
);
|
||||
await layerManager.moveLayerBetweenGroups(draggedLayer.id, fromParentId, toParentId, newIndex);
|
||||
} else {
|
||||
// 回退方案:手动更新图层关系
|
||||
// 从源父图层中移除
|
||||
const childIndex = sourceParent.children.findIndex(
|
||||
(child) => child.id === draggedLayer.id
|
||||
);
|
||||
const childIndex = sourceParent.children.findIndex((child) => child.id === draggedLayer.id);
|
||||
if (childIndex !== -1) {
|
||||
sourceParent.children.splice(childIndex, 1);
|
||||
|
||||
@@ -1585,19 +1501,11 @@ async function moveGroupToGroup(
|
||||
>
|
||||
<SvgIcon name="CPlusTop" size="16"></SvgIcon>
|
||||
</div>
|
||||
<div
|
||||
class="add-layer-btn action-btn"
|
||||
@click="addLayer"
|
||||
:title="$t('添加图层')"
|
||||
>
|
||||
<div class="add-layer-btn action-btn" @click="addLayer" :title="$t('添加图层')">
|
||||
<SvgIcon name="CPlus" size="16"></SvgIcon>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="select-all-btn action-btn"
|
||||
@click="selectAllLayers"
|
||||
:title="$t('全选图层')"
|
||||
>
|
||||
<div class="select-all-btn action-btn" @click="selectAllLayers" :title="$t('全选图层')">
|
||||
<SvgIcon name="CCheckbox" size="16"></SvgIcon>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1668,15 +1576,11 @@ async function moveGroupToGroup(
|
||||
:editing-name="editingLayerName"
|
||||
:can-delete="false"
|
||||
:isHidenDragHandle="true"
|
||||
@toggle-visibility="
|
||||
(...args) => forwardEvent('toggle-layer-visibility', ...args)
|
||||
"
|
||||
@toggle-visibility="(...args) => forwardEvent('toggle-layer-visibility', ...args)"
|
||||
@edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)"
|
||||
@edit-cancel="(...args) => forwardEvent('edit-cancel', ...args)"
|
||||
@edit-keydown="(...args) => forwardEvent('edit-keydown', ...args)"
|
||||
@update:editing-name="
|
||||
(...args) => forwardEvent('update:editing-name', ...args)
|
||||
"
|
||||
@update:editing-name="(...args) => forwardEvent('update:editing-name', ...args)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -374,10 +374,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
// 创建状态管理器
|
||||
stateManager.value = new LiquifyStateManager(
|
||||
props.canvas,
|
||||
realtimeUpdater.value
|
||||
);
|
||||
stateManager.value = new LiquifyStateManager(props.canvas, realtimeUpdater.value);
|
||||
}
|
||||
|
||||
// 监听画布事件
|
||||
@@ -446,12 +443,7 @@ function showPanel(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"显示液化面板,目标对象:",
|
||||
targetObj.type,
|
||||
"图层ID:",
|
||||
targetLayerIdValue
|
||||
);
|
||||
console.log("显示液化面板,目标对象:", targetObj.type, "图层ID:", targetLayerIdValue);
|
||||
|
||||
// 更新面板状态,但保持当前模式不变
|
||||
// 只有在首次显示面板或面板已关闭时才重置模式
|
||||
@@ -521,8 +513,8 @@ function showPanel(event) {
|
||||
const status = props.liquifyManager.getStatus
|
||||
? props.liquifyManager.getStatus()
|
||||
: props.liquifyManager.enhancedManager
|
||||
? props.liquifyManager.enhancedManager.getStatus()
|
||||
: {};
|
||||
? props.liquifyManager.enhancedManager.getStatus()
|
||||
: {};
|
||||
|
||||
webglAvailable.value = status.isWebGLAvailable || false;
|
||||
|
||||
@@ -553,8 +545,8 @@ function showPanel(event) {
|
||||
const status = props.liquifyManager.getStatus
|
||||
? props.liquifyManager.getStatus()
|
||||
: props.liquifyManager.enhancedManager
|
||||
? props.liquifyManager.enhancedManager.getStatus()
|
||||
: {};
|
||||
? props.liquifyManager.enhancedManager.getStatus()
|
||||
: {};
|
||||
|
||||
webglAvailable.value = status.isWebGLAvailable || false;
|
||||
|
||||
@@ -613,11 +605,7 @@ function setTargetObject(obj) {
|
||||
|
||||
// 确保对象有唯一ID
|
||||
if (!obj.id && !obj.objectId && !obj.uid) {
|
||||
obj.id =
|
||||
"liquify_target_" +
|
||||
Date.now() +
|
||||
"_" +
|
||||
Math.random().toString(36).substr(2, 9);
|
||||
obj.id = "liquify_target_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
|
||||
targetObject.value = obj;
|
||||
@@ -685,9 +673,7 @@ function updateParam(paramName, value) {
|
||||
`set${paramName.charAt(0).toUpperCase() + paramName.slice(1)}`
|
||||
] === "function"
|
||||
) {
|
||||
props.liquifyManager[
|
||||
`set${paramName.charAt(0).toUpperCase() + paramName.slice(1)}`
|
||||
](value);
|
||||
props.liquifyManager[`set${paramName.charAt(0).toUpperCase() + paramName.slice(1)}`](value);
|
||||
} else {
|
||||
console.warn(`❌ 液化管理器不支持设置参数: ${paramName}`);
|
||||
}
|
||||
@@ -770,19 +756,9 @@ async function getCurrentImageData(targetObject) {
|
||||
tempCtx.drawImage(element, 0, 0, tempCanvas.width, tempCanvas.height);
|
||||
|
||||
// 获取ImageData
|
||||
const imageData = tempCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
|
||||
console.log(
|
||||
"✅ 成功获取当前图像状态,尺寸:",
|
||||
imageData.width,
|
||||
"x",
|
||||
imageData.height
|
||||
);
|
||||
console.log("✅ 成功获取当前图像状态,尺寸:", imageData.width, "x", imageData.height);
|
||||
return imageData;
|
||||
} catch (error) {
|
||||
console.warn("获取当前图像数据失败:", error);
|
||||
@@ -861,9 +837,7 @@ async function handleMouseDown(event) {
|
||||
const imageCoords = _convertFabricCoordsToImageCoords(pointer.x, pointer.y);
|
||||
if (imageCoords) {
|
||||
props.liquifyManager.startLiquifyOperation(imageCoords.x, imageCoords.y);
|
||||
console.log(
|
||||
`开始液化操作,图像坐标: (${imageCoords.x}, ${imageCoords.y})`
|
||||
);
|
||||
console.log(`开始液化操作,图像坐标: (${imageCoords.x}, ${imageCoords.y})`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -959,27 +933,15 @@ async function handleMouseUp() {
|
||||
// 尝试从实时更新器获取当前图像数据
|
||||
if (realtimeUpdater.value) {
|
||||
try {
|
||||
const currentImageElement =
|
||||
realtimeUpdater.value.getTargetObject()?._element;
|
||||
const currentImageElement = realtimeUpdater.value.getTargetObject()?._element;
|
||||
if (currentImageElement) {
|
||||
// 从当前图像元素创建ImageData
|
||||
const tempCanvas = document.createElement("canvas");
|
||||
tempCanvas.width = initialImageData.value.width;
|
||||
tempCanvas.height = initialImageData.value.height;
|
||||
const tempCtx = tempCanvas.getContext("2d");
|
||||
tempCtx.drawImage(
|
||||
currentImageElement,
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
finalImageData = tempCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
tempCtx.drawImage(currentImageElement, 0, 0, tempCanvas.width, tempCanvas.height);
|
||||
finalImageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
|
||||
console.log(
|
||||
"✅ 从实时更新器获取最终图像数据成功,尺寸:",
|
||||
@@ -1000,19 +962,8 @@ async function handleMouseUp() {
|
||||
tempCanvas.width = initialImageData.value.width;
|
||||
tempCanvas.height = initialImageData.value.height;
|
||||
const tempCtx = tempCanvas.getContext("2d");
|
||||
tempCtx.drawImage(
|
||||
currentTarget._element,
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
finalImageData = tempCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
tempCtx.drawImage(currentTarget._element, 0, 0, tempCanvas.width, tempCanvas.height);
|
||||
finalImageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
|
||||
console.log(
|
||||
"✅ 从目标对象获取最终图像数据成功,尺寸:",
|
||||
@@ -1097,14 +1048,7 @@ async function applyLiquifyAtPoint(x, y) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"原始坐标:",
|
||||
x,
|
||||
y,
|
||||
"转换后图像坐标:",
|
||||
imageCoords.x,
|
||||
imageCoords.y
|
||||
);
|
||||
console.log("原始坐标:", x, y, "转换后图像坐标:", imageCoords.x, imageCoords.y);
|
||||
|
||||
// 获取当前参数
|
||||
const params = {
|
||||
@@ -1169,8 +1113,7 @@ async function applyLiquifyAtPoint(x, y) {
|
||||
targetObject.value = updatedObject;
|
||||
// 如果对象有新的ID,也要更新ID
|
||||
if (updatedObject.id || updatedObject.objectId || updatedObject.uid) {
|
||||
targetObjectId.value =
|
||||
updatedObject.id || updatedObject.objectId || updatedObject.uid;
|
||||
targetObjectId.value = updatedObject.id || updatedObject.objectId || updatedObject.uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1184,8 +1127,7 @@ async function applyLiquifyAtPoint(x, y) {
|
||||
if (avgOperationTime.value === 0) {
|
||||
avgOperationTime.value = operationTime;
|
||||
} else {
|
||||
avgOperationTime.value =
|
||||
0.7 * avgOperationTime.value + 0.3 * operationTime;
|
||||
avgOperationTime.value = 0.7 * avgOperationTime.value + 0.3 * operationTime;
|
||||
}
|
||||
|
||||
// 将性能指标传递给状态管理器
|
||||
@@ -1265,7 +1207,7 @@ async function _updateImageOnCanvas(imageData) {
|
||||
}
|
||||
} else {
|
||||
// 拖拽结束后进行完整的对象替换
|
||||
await _replaceTargetObjectWithNewImage(dataURL);
|
||||
// await _replaceTargetObjectWithNewImage(dataURL);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("更新画布图像时出错:", error);
|
||||
@@ -1325,9 +1267,7 @@ async function _replaceTargetWithNewImage(dataURL) {
|
||||
if (layer.type === "background" || layer.isBackground) {
|
||||
layer.fabricObject = img;
|
||||
} else if (layer.fabricObjects) {
|
||||
const objIndex = layer.fabricObjects.findIndex(
|
||||
(obj) => obj.id === img.id
|
||||
);
|
||||
const objIndex = layer.fabricObjects.findIndex((obj) => obj.id === img.id);
|
||||
if (objIndex !== -1) {
|
||||
layer.fabricObjects[objIndex] = img;
|
||||
}
|
||||
@@ -1364,10 +1304,7 @@ function _convertFabricCoordsToImageCoords(fabricX, fabricY) {
|
||||
const matrix = fabric.util.invertTransform(transform);
|
||||
|
||||
// 应用逆变换,将画布坐标转换为对象本地坐标
|
||||
const localPoint = fabric.util.transformPoint(
|
||||
new fabric.Point(fabricX, fabricY),
|
||||
matrix
|
||||
);
|
||||
const localPoint = fabric.util.transformPoint(new fabric.Point(fabricX, fabricY), matrix);
|
||||
|
||||
// 获取图像的原始尺寸(未缩放前)
|
||||
const imageWidth = originalImageData.value.width;
|
||||
@@ -1403,12 +1340,7 @@ function _convertFabricCoordsToImageCoords(fabricX, fabricY) {
|
||||
);
|
||||
|
||||
// 检查坐标是否在图像范围内
|
||||
if (
|
||||
imageX < 0 ||
|
||||
imageX >= imageWidth ||
|
||||
imageY < 0 ||
|
||||
imageY >= imageHeight
|
||||
) {
|
||||
if (imageX < 0 || imageX >= imageWidth || imageY < 0 || imageY >= imageHeight) {
|
||||
console.warn(
|
||||
`坐标超出图像范围: (${imageX}, ${imageY}), 图像尺寸: ${imageWidth}x${imageHeight}`
|
||||
);
|
||||
@@ -1607,7 +1539,9 @@ function stopPressTimer() {
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s, transform 0.3s;
|
||||
transition:
|
||||
opacity 0.3s,
|
||||
transform 0.3s;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
|
||||
@@ -68,9 +68,7 @@ onBeforeUnmount(() => {
|
||||
<div class="minimap-container">
|
||||
<div class="minimap-header">
|
||||
<span>画布小地图</span>
|
||||
<button class="minimap-refresh" @click="forceRefresh" title="刷新小地图">
|
||||
⟳
|
||||
</button>
|
||||
<button class="minimap-refresh" @click="forceRefresh" title="刷新小地图">⟳</button>
|
||||
</div>
|
||||
<div class="minimap-content" ref="minimapContainerRef">
|
||||
<!-- 不再需要直接提供canvas引用,由MinimapManager内部创建 -->
|
||||
|
||||
@@ -10,30 +10,21 @@
|
||||
|
||||
<div class="tool-types">
|
||||
<div
|
||||
:class="[
|
||||
'tool-btn',
|
||||
{ active: selectionType === OperationType.LASSO },
|
||||
]"
|
||||
:class="['tool-btn', { active: selectionType === OperationType.LASSO }]"
|
||||
@click="setSelectionType(OperationType.LASSO)"
|
||||
>
|
||||
<svg-icon name="CFree" size="26" />
|
||||
<span>{{ $t("手绘") }}</span>
|
||||
</div>
|
||||
<div
|
||||
:class="[
|
||||
'tool-btn',
|
||||
{ active: selectionType === OperationType.LASSO_RECTANGLE },
|
||||
]"
|
||||
:class="['tool-btn', { active: selectionType === OperationType.LASSO_RECTANGLE }]"
|
||||
@click="setSelectionType(OperationType.LASSO_RECTANGLE)"
|
||||
>
|
||||
<svg-icon name="CRectangle" size="32" />
|
||||
<span>{{ $t("矩形") }}</span>
|
||||
</div>
|
||||
<div
|
||||
:class="[
|
||||
'tool-btn',
|
||||
{ active: selectionType === OperationType.LASSO_ELLIPSE },
|
||||
]"
|
||||
:class="['tool-btn', { active: selectionType === OperationType.LASSO_ELLIPSE }]"
|
||||
@click="setSelectionType(OperationType.LASSO_ELLIPSE)"
|
||||
>
|
||||
<svg-icon name="CEllipse" size="30" />
|
||||
@@ -159,9 +150,7 @@
|
||||
<div class="dialog-container">
|
||||
<div class="dialog-header">
|
||||
<h3>{{ $t("选择填充颜色") }}</h3>
|
||||
<button class="close-dialog-btn" @click="cancelColorPicker">
|
||||
×
|
||||
</button>
|
||||
<button class="close-dialog-btn" @click="cancelColorPicker">×</button>
|
||||
</div>
|
||||
<div class="dialog-content">
|
||||
<input type="color" v-model="fillColor" class="color-picker" />
|
||||
@@ -249,6 +238,7 @@ onMounted(() => {
|
||||
checkSelectionStatus();
|
||||
|
||||
// 设置选区状态变化的回调
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
props.selectionManager.onSelectionChanged = () => {
|
||||
checkSelectionStatus();
|
||||
};
|
||||
@@ -321,8 +311,7 @@ function setSelectionType(type) {
|
||||
*/
|
||||
function checkSelectionStatus() {
|
||||
hasSelection.value =
|
||||
props.selectionManager &&
|
||||
props.selectionManager.getSelectionObject() !== null;
|
||||
props.selectionManager && props.selectionManager.getSelectionObject() !== null;
|
||||
|
||||
// 同步羽化值
|
||||
if (hasSelection.value) {
|
||||
@@ -836,7 +825,9 @@ function confirmColorPicker() {
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s, transform 0.3s;
|
||||
transition:
|
||||
opacity 0.3s,
|
||||
transform 0.3s;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<!-- filepath: /Users/aaron/work/pc/air/canvasEdit/src/components/CanvasEditor/components/TextEditorPanel.vue -->
|
||||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="text-editor-panel"
|
||||
:class="{ 'is-active': visible }"
|
||||
>
|
||||
<div v-if="visible" class="text-editor-panel" :class="{ 'is-active': visible }">
|
||||
<div class="text-editor-panel-header">
|
||||
<div class="header-btn import-btn">编辑文本样式</div>
|
||||
<div class="header-actions">
|
||||
@@ -185,10 +181,7 @@
|
||||
<div class="bg-header">字体色</div>
|
||||
<div class="bg-options">
|
||||
<div class="style-btn color-btn" @click="openColorPicker('text')">
|
||||
<div
|
||||
class="style-icon color-icon"
|
||||
:style="{ backgroundColor: textColor }"
|
||||
></div>
|
||||
<div class="style-icon color-icon" :style="{ backgroundColor: textColor }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -196,22 +189,15 @@
|
||||
<div class="background-controls">
|
||||
<div class="bg-header">背景色</div>
|
||||
<div class="bg-options">
|
||||
<div
|
||||
class="style-btn color-btn"
|
||||
@click="openColorPicker('background')"
|
||||
>
|
||||
<div class="style-btn color-btn" @click="openColorPicker('background')">
|
||||
<div
|
||||
class="style-icon color-icon"
|
||||
:style="{
|
||||
backgroundColor: hasTransparentBg
|
||||
? 'transparent'
|
||||
: backgroundColor,
|
||||
backgroundColor: hasTransparentBg ? 'transparent' : backgroundColor,
|
||||
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)'
|
||||
: 'none',
|
||||
backgroundSize: hasTransparentBg
|
||||
? '8px 8px, 8px 8px'
|
||||
: 'auto',
|
||||
backgroundSize: hasTransparentBg ? '8px 8px, 8px 8px' : 'auto',
|
||||
backgroundPosition: hasTransparentBg ? '0 0, 4px 4px' : '0 0',
|
||||
}"
|
||||
></div>
|
||||
@@ -232,18 +218,11 @@
|
||||
<div v-if="showColorPicker" class="color-picker-modal">
|
||||
<div class="color-picker-container">
|
||||
<div class="color-picker-header">
|
||||
<span>{{
|
||||
colorPickerMode === "text" ? "选择文字颜色" : "选择背景颜色"
|
||||
}}</span>
|
||||
<span>{{ colorPickerMode === "text" ? "选择文字颜色" : "选择背景颜色" }}</span>
|
||||
<div class="close-color-picker" @click="closeColorPicker">×</div>
|
||||
</div>
|
||||
<div class="color-picker-content">
|
||||
<input
|
||||
type="color"
|
||||
v-model="currentColor"
|
||||
@change="updateColor"
|
||||
class="color-input"
|
||||
/>
|
||||
<input type="color" v-model="currentColor" @change="updateColor" class="color-input" />
|
||||
<div class="color-presets">
|
||||
<div
|
||||
v-for="(color, index) in colorPresets"
|
||||
@@ -253,9 +232,7 @@
|
||||
@click="selectPresetColor(color)"
|
||||
></div>
|
||||
</div>
|
||||
<div class="confirm-color-btn" @click="confirmColorSelection">
|
||||
确定
|
||||
</div>
|
||||
<div class="confirm-color-btn" @click="confirmColorSelection">确定</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -316,7 +293,7 @@ export default {
|
||||
// 新增属性
|
||||
const charSpacingPercent = ref(0);
|
||||
const textSpacing = ref(0);
|
||||
const baseline = ref(0);
|
||||
// const baseline = ref(0);
|
||||
const showColorPicker = ref(false);
|
||||
const colorPickerMode = ref("text"); // 'text' 或 'background'
|
||||
const currentColor = ref("#000000");
|
||||
@@ -406,8 +383,7 @@ export default {
|
||||
textColor.value = textObject.value.fill || "#000000";
|
||||
backgroundColor.value = textObject.value.textBackgroundColor || "#ffffff";
|
||||
hasTransparentBg.value = !textObject.value.textBackgroundColor;
|
||||
opacity.value =
|
||||
textObject.value.opacity !== undefined ? textObject.value.opacity : 1;
|
||||
opacity.value = textObject.value.opacity !== undefined ? textObject.value.opacity : 1;
|
||||
|
||||
// 样式
|
||||
fontWeight.value = textObject.value.fontWeight || "normal";
|
||||
@@ -422,7 +398,7 @@ export default {
|
||||
// 转换字符间距为百分比显示
|
||||
charSpacingPercent.value = charSpacing.value / 10;
|
||||
textSpacing.value = charSpacingPercent.value; // 暂用相同值
|
||||
baseline.value = 0; // Fabric.js没有直接支持基线偏移,用0初始化
|
||||
// baseline.value = 0; // Fabric.js没有直接支持基线偏移,用0初始化
|
||||
};
|
||||
|
||||
const selectFont = (fontName) => {
|
||||
@@ -459,8 +435,7 @@ export default {
|
||||
// 颜色选择器相关功能
|
||||
const openColorPicker = (mode) => {
|
||||
colorPickerMode.value = mode;
|
||||
currentColor.value =
|
||||
mode === "text" ? textColor.value : backgroundColor.value;
|
||||
currentColor.value = mode === "text" ? textColor.value : backgroundColor.value;
|
||||
showColorPicker.value = true;
|
||||
};
|
||||
|
||||
@@ -602,12 +577,12 @@ export default {
|
||||
executeCommand(command);
|
||||
};
|
||||
|
||||
// 基线更新 (Fabric.js没有直接支持,可能需要自定义实现)
|
||||
const updateBaseline = () => {
|
||||
console.log("基线调整功能待实现", baseline.value);
|
||||
// 注意:Fabric.js 5没有直接支持基线调整
|
||||
// 可能需要通过自定义处理或CSS方式实现
|
||||
};
|
||||
// // 基线更新 (Fabric.js没有直接支持,可能需要自定义实现)
|
||||
// const updateBaseline = () => {
|
||||
// // console.log("基线调整功能待实现", baseline.value);
|
||||
// // 注意:Fabric.js 5没有直接支持基线调整
|
||||
// // 可能需要通过自定义处理或CSS方式实现
|
||||
// };
|
||||
|
||||
// 透明度更新
|
||||
const updateOpacity = () => {
|
||||
@@ -662,7 +637,7 @@ export default {
|
||||
lineHeight,
|
||||
charSpacingPercent,
|
||||
textSpacing,
|
||||
baseline,
|
||||
// baseline,
|
||||
showColorPicker,
|
||||
colorPickerMode,
|
||||
currentColor,
|
||||
@@ -690,7 +665,7 @@ export default {
|
||||
updateCharSpacing,
|
||||
updateTextSpacing,
|
||||
updateLineHeight,
|
||||
updateBaseline,
|
||||
// updateBaseline,
|
||||
updateOpacity,
|
||||
executeCommand,
|
||||
};
|
||||
|
||||
@@ -259,10 +259,7 @@ function handleKeyDown(event) {
|
||||
const key = event.key.toUpperCase();
|
||||
|
||||
// 当处于输入状态时不触发快捷键
|
||||
if (
|
||||
event.target.tagName === "INPUT" ||
|
||||
event.target.tagName === "TEXTAREA"
|
||||
) {
|
||||
if (event.target.tagName === "INPUT" || event.target.tagName === "TEXTAREA") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -312,10 +309,7 @@ const handleToolClick = (tool) => {
|
||||
/>
|
||||
|
||||
<!-- 自定义工具栏按钮插槽 -->
|
||||
<slot
|
||||
name="customTools"
|
||||
:tool-button-props="{ activeTool, canUndo, canRedo }"
|
||||
/>
|
||||
<slot name="customTools" :tool-button-props="{ activeTool, canUndo, canRedo }" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -7,10 +7,7 @@
|
||||
@touchstart.prevent="startTouchSliding"
|
||||
@click="handleClick"
|
||||
>
|
||||
<div
|
||||
class="slider-fill"
|
||||
:style="{ height: `${displayPercentage}%` }"
|
||||
></div>
|
||||
<div class="slider-fill" :style="{ height: `${displayPercentage}%` }"></div>
|
||||
<div
|
||||
class="slider-thumb"
|
||||
:style="{
|
||||
@@ -154,9 +151,7 @@ function clearHideTooltipTimer() {
|
||||
|
||||
// 计算tooltip的可见性,优先使用props中的showTooltip,否则使用内部状态
|
||||
const tooltipVisible = computed(() => {
|
||||
return props.showTooltip !== undefined
|
||||
? props.showTooltip
|
||||
: internalShowTooltip.value;
|
||||
return props.showTooltip !== undefined ? props.showTooltip : internalShowTooltip.value;
|
||||
});
|
||||
|
||||
// 更新tooltip状态的方法
|
||||
@@ -250,8 +245,7 @@ function updateValueFromMousePosition(event) {
|
||||
}
|
||||
} else {
|
||||
// 检查是否可以进入吸附状态
|
||||
const snapPercentage =
|
||||
(props.snapThreshold / trackHeight) * (props.max - props.min);
|
||||
const snapPercentage = (props.snapThreshold / trackHeight) * (props.max - props.min);
|
||||
|
||||
// 检查是否接近预设值,增加吸附判定范围
|
||||
for (const preset of props.presets) {
|
||||
@@ -417,8 +411,7 @@ function updateValueFromTouchPosition(event) {
|
||||
}
|
||||
} else {
|
||||
// 检查是否可以进入吸附状态
|
||||
const snapPercentage =
|
||||
(props.snapThreshold / trackHeight) * (props.max - props.min);
|
||||
const snapPercentage = (props.snapThreshold / trackHeight) * (props.max - props.min);
|
||||
|
||||
// 检查是否接近预设值,增加吸附判定范围
|
||||
for (const preset of props.presets) {
|
||||
@@ -500,9 +493,7 @@ onMounted(() => {
|
||||
// 添加全局点击事件监听
|
||||
if (props.closeOnOutsideClick) {
|
||||
// 使用 setTimeout 确保点击事件在其他处理程序之后执行
|
||||
window.addEventListener("click", (e) =>
|
||||
setTimeout(() => handleOutsideClick(e), 0)
|
||||
);
|
||||
window.addEventListener("click", (e) => setTimeout(() => handleOutsideClick(e), 0));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -616,7 +607,9 @@ onBeforeUnmount(() => {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 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;
|
||||
z-index: 10;
|
||||
|
||||
@@ -664,7 +657,9 @@ onBeforeUnmount(() => {
|
||||
// 淡入淡出动画
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s, transform 0.3s;
|
||||
transition:
|
||||
opacity 0.3s,
|
||||
transform 0.3s;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
|
||||
@@ -34,14 +34,10 @@ import SelectionPanel from "./components/SelectionPanel.vue"; // 引入选区面
|
||||
import { LayerType, OperationType } from "./utils/layerHelper.js";
|
||||
import { ToolManager } from "./managers/toolManager.js";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import {
|
||||
uploadImageAndCreateLayer,
|
||||
loadImageUrlToLayer,
|
||||
loadImage,
|
||||
} from "./utils/imageHelper.js";
|
||||
import { uploadImageAndCreateLayer, loadImageUrlToLayer, loadImage } from "./utils/imageHelper.js";
|
||||
// import MinimapPanel from "./components/MinimapPanel.vue";
|
||||
const KeyboardShortcutHelp = defineAsyncComponent(() =>
|
||||
import("./components/KeyboardShortcutHelp.vue")
|
||||
const KeyboardShortcutHelp = defineAsyncComponent(
|
||||
() => import("./components/KeyboardShortcutHelp.vue")
|
||||
);
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -318,11 +314,7 @@ onMounted(async () => {
|
||||
await layerManager.initializeLayers();
|
||||
}
|
||||
|
||||
if (
|
||||
props.enabledRedGreenMode &&
|
||||
props.clothingImageUrl &&
|
||||
props.redGreenImageUrl
|
||||
) {
|
||||
if (props.enabledRedGreenMode && props.clothingImageUrl && props.redGreenImageUrl) {
|
||||
canvasManager.canvas.fill = "#fff"; // 设置画布背景色为白色 // 初始化红绿图模式管理器
|
||||
redGreenModeManager = new RedGreenModeManager({
|
||||
canvas: canvasManager.canvas,
|
||||
@@ -369,10 +361,7 @@ onMounted(async () => {
|
||||
console.error("更换底图失败:", error);
|
||||
}
|
||||
|
||||
canvasManager?.centerBackgroundLayer?.(
|
||||
canvasManager.canvas.width,
|
||||
canvasManager.canvas.height
|
||||
);
|
||||
canvasManager?.centerBackgroundLayer?.(canvasManager.canvas.width, canvasManager.canvas.height);
|
||||
}
|
||||
|
||||
// // 设置固定图层是否可擦除
|
||||
@@ -395,7 +384,7 @@ onMounted(async () => {
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
// 初始状态下生成所有预览图
|
||||
canvasManager.updateAllThumbnails();
|
||||
canvasManager?.updateAllThumbnails?.();
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
@@ -559,9 +548,7 @@ function moveLayerDown(layerId) {
|
||||
function removeLayer(layerId) {
|
||||
// Check if this is the last layer - prevent deletion
|
||||
if (layers.value.length <= 2) {
|
||||
console.warn(
|
||||
"Cannot delete the last layer. At least one layer must remain."
|
||||
);
|
||||
console.warn("Cannot delete the last layer. At least one layer must remain.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -667,9 +654,7 @@ function handleLayersReorder(reorderData) {
|
||||
const success = layerManager.reorderLayers(oldIndex, newIndex, layerId);
|
||||
|
||||
if (success) {
|
||||
console.log(
|
||||
`图层 ${layerId} 已从位置 ${oldIndex} 移动到位置 ${newIndex}`
|
||||
);
|
||||
console.log(`图层 ${layerId} 已从位置 ${oldIndex} 移动到位置 ${newIndex}`);
|
||||
|
||||
// 更新画布渲染顺序
|
||||
if (canvasManager) {
|
||||
@@ -686,12 +671,7 @@ function handleChildLayersReorder(reorderData) {
|
||||
const { parentId, oldIndex, newIndex, layerId } = reorderData;
|
||||
|
||||
if (layerManager && layerManager.reorderChildLayers) {
|
||||
const success = layerManager.reorderChildLayers(
|
||||
parentId,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
layerId
|
||||
);
|
||||
const success = layerManager.reorderChildLayers(parentId, oldIndex, newIndex, layerId);
|
||||
|
||||
if (success) {
|
||||
console.log(
|
||||
@@ -851,8 +831,7 @@ defineExpose({
|
||||
* @returns {Object} 优化结果统计
|
||||
*/
|
||||
optimizeLayerStructure() {
|
||||
if (!layerManager)
|
||||
return { removedEmptyLayers: 0, mergedLayers: 0, reorderedLayers: 0 };
|
||||
if (!layerManager) return { removedEmptyLayers: 0, mergedLayers: 0, reorderedLayers: 0 };
|
||||
return layerManager.optimizeLayerStructure();
|
||||
},
|
||||
|
||||
@@ -965,10 +944,7 @@ defineExpose({
|
||||
<!-- <MinimapPanel v-if="minimapEnabled" :minimapManager="minimapManager" /> -->
|
||||
|
||||
<!-- 笔刷控制面板 -->
|
||||
<BrushControlPanel
|
||||
v-if="canvasManagerLoaded"
|
||||
:activeTool="activeTool"
|
||||
/>
|
||||
<BrushControlPanel v-if="canvasManagerLoaded" :activeTool="activeTool" />
|
||||
|
||||
<!-- 文本编辑面板 -->
|
||||
<TextEditorPanel
|
||||
@@ -1001,11 +977,7 @@ defineExpose({
|
||||
<div class="zoom-info">
|
||||
缩放: {{ currentZoom }}%
|
||||
<button class="reset-zoom" @click="resetZoom">重置视图</button>
|
||||
<button
|
||||
class="help-btn"
|
||||
@click="toggleShortcutHelp"
|
||||
title="查看快捷键和触控操作"
|
||||
>
|
||||
<button class="help-btn" @click="toggleShortcutHelp" title="查看快捷键和触控操作">
|
||||
?
|
||||
</button>
|
||||
</div>
|
||||
@@ -1045,11 +1017,7 @@ defineExpose({
|
||||
</div> -->
|
||||
|
||||
<!-- 快捷键帮助模态框 -->
|
||||
<div
|
||||
v-if="showShortcutHelp"
|
||||
class="modal-overlay"
|
||||
@click="showShortcutHelp = false"
|
||||
>
|
||||
<div v-if="showShortcutHelp" class="modal-overlay" @click="showShortcutHelp = false">
|
||||
<div class="modal-content" @click.stop>
|
||||
<button class="close-modal" @click="showShortcutHelp = false">×</button>
|
||||
<KeyboardShortcutHelp />
|
||||
@@ -1126,30 +1094,20 @@ defineExpose({
|
||||
--offsetY: 0px;
|
||||
--size: 8px;
|
||||
--color: #dedcdc;
|
||||
background-image: -webkit-linear-gradient(
|
||||
background-image:
|
||||
-webkit-linear-gradient(
|
||||
45deg,
|
||||
var(--color) 25%,
|
||||
transparent 0,
|
||||
transparent 75%,
|
||||
var(--color) 0
|
||||
),
|
||||
-webkit-linear-gradient(45deg, var(--color) 25%, transparent 0, transparent
|
||||
75%, var(--color) 0);
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
var(--color) 25%,
|
||||
transparent 0,
|
||||
transparent 75%,
|
||||
var(--color) 0
|
||||
),
|
||||
linear-gradient(
|
||||
45deg,
|
||||
var(--color) 25%,
|
||||
transparent 0,
|
||||
transparent 75%,
|
||||
var(--color) 0
|
||||
);
|
||||
background-position: var(--offsetX) var(--offsetY),
|
||||
-webkit-linear-gradient(45deg, var(--color) 25%, transparent 0, transparent 75%, var(--color) 0);
|
||||
background-image:
|
||||
linear-gradient(45deg, var(--color) 25%, transparent 0, 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));
|
||||
background-size: calc(var(--size) * 2) calc(var(--size) * 2);
|
||||
}
|
||||
@@ -1369,7 +1327,9 @@ button:hover {
|
||||
// 淡入淡出动画
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s, transform 0.3s;
|
||||
transition:
|
||||
opacity 0.3s,
|
||||
transform 0.3s;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
|
||||
@@ -97,8 +97,7 @@ export class BrushIndicator {
|
||||
if (!this.staticCanvas || !this.canvas) return;
|
||||
|
||||
// 检查是否为笔刷或橡皮擦模式,非相关模式直接返回
|
||||
const isBrushMode =
|
||||
this.canvas.isDrawingMode && this.canvas.freeDrawingBrush;
|
||||
const isBrushMode = this.canvas.isDrawingMode && this.canvas.freeDrawingBrush;
|
||||
const isEraserMode =
|
||||
this.canvas.isDrawingMode &&
|
||||
this.canvas.freeDrawingBrush &&
|
||||
@@ -136,10 +135,7 @@ export class BrushIndicator {
|
||||
const currentVpt = this.canvas.viewportTransform;
|
||||
if (
|
||||
currentVpt &&
|
||||
!this._areViewportTransformsEqual(
|
||||
currentVpt,
|
||||
this._lastCanvasState.viewportTransform
|
||||
)
|
||||
!this._areViewportTransformsEqual(currentVpt, this._lastCanvasState.viewportTransform)
|
||||
) {
|
||||
this.staticCanvas.setViewportTransform([...currentVpt]);
|
||||
this._lastCanvasState.viewportTransform = [...currentVpt];
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { fabric } from "fabric-with-all";
|
||||
import initAligningGuidelines, {
|
||||
initCenteringGuidelines,
|
||||
} from "../utils/helperLine";
|
||||
import initAligningGuidelines, { initCenteringGuidelines } from "../utils/helperLine";
|
||||
import { ThumbnailManager } from "./ThumbnailManager";
|
||||
import { ExportManager } from "./ExportManager";
|
||||
import {
|
||||
@@ -16,11 +14,7 @@ import { CanvasEventManager } from "./events/CanvasEventManager";
|
||||
import CanvasConfig from "../config/canvasConfig";
|
||||
import { RedGreenModeManager } from "./RedGreenModeManager";
|
||||
import { EraserStateManager } from "./EraserStateManager";
|
||||
import {
|
||||
deepClone,
|
||||
generateId,
|
||||
optimizeCanvasRendering,
|
||||
} from "../utils/helper";
|
||||
import { deepClone, generateId, optimizeCanvasRendering } from "../utils/helper";
|
||||
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
|
||||
import { isFunction } from "lodash-es";
|
||||
import {
|
||||
@@ -155,10 +149,7 @@ export class CanvasManager {
|
||||
return false;
|
||||
};
|
||||
|
||||
this.eraserStateManager = new EraserStateManager(
|
||||
this.canvas,
|
||||
this.layerManager
|
||||
);
|
||||
this.eraserStateManager = new EraserStateManager(this.canvas, this.layerManager);
|
||||
|
||||
// 监听擦除开始事件
|
||||
this.canvas.on("erasing:start", () => {
|
||||
@@ -179,17 +170,12 @@ export class CanvasManager {
|
||||
}
|
||||
|
||||
// 更新交互性
|
||||
command &&
|
||||
(await this.layerManager?.updateLayersObjectsInteractivity?.());
|
||||
command && (await this.layerManager?.updateLayersObjectsInteractivity?.());
|
||||
|
||||
this.thumbnailManager?.generateLayerThumbnail(
|
||||
this.layerManager?.activeLayerId?.value
|
||||
);
|
||||
this.thumbnailManager?.generateLayerThumbnail(this.layerManager?.activeLayerId?.value);
|
||||
|
||||
// 固定图层 的擦除也需要重新生成缩略图 要判断 当前固定图层是否锁定
|
||||
const fixedLayer = this.layers?.value?.find(
|
||||
(layer) => layer.isFixed && !layer.locked
|
||||
);
|
||||
const fixedLayer = this.layers?.value?.find((layer) => layer.isFixed && !layer.locked);
|
||||
// 如果有固定图层且未锁定,则生成缩略图
|
||||
fixedLayer &&
|
||||
this.isFixedErasable &&
|
||||
@@ -378,9 +364,7 @@ export class CanvasManager {
|
||||
// 设置固定图层的可擦除状态
|
||||
layer.locked = flag;
|
||||
// 更新画布对象的erasable属性
|
||||
const fabricObject = this.canvas
|
||||
.getObjects()
|
||||
.find((obj) => obj.id === layer.id);
|
||||
const fabricObject = this.canvas.getObjects().find((obj) => obj.id === layer.id);
|
||||
if (fabricObject) {
|
||||
fabricObject.erasable = flag;
|
||||
fabricObject.set("erasable", flag);
|
||||
@@ -492,9 +476,7 @@ export class CanvasManager {
|
||||
const deltaY = backgroundObject.top - backgroundOldTop;
|
||||
|
||||
// 将相同的偏移量应用到所有其他对象上
|
||||
const otherObjects = visibleObjects.filter(
|
||||
(obj) => obj !== backgroundObject
|
||||
);
|
||||
const otherObjects = visibleObjects.filter((obj) => obj !== backgroundObject);
|
||||
|
||||
otherObjects.forEach((obj) => {
|
||||
obj.set({
|
||||
@@ -597,8 +579,7 @@ export class CanvasManager {
|
||||
if (!backgroundLayerObject) return;
|
||||
|
||||
const bgWidth = backgroundLayerObject.width * backgroundLayerObject.scaleX;
|
||||
const bgHeight =
|
||||
backgroundLayerObject.height * backgroundLayerObject.scaleY;
|
||||
const bgHeight = backgroundLayerObject.height * backgroundLayerObject.scaleY;
|
||||
const left = backgroundLayerObject.left;
|
||||
const top = backgroundLayerObject.top;
|
||||
|
||||
@@ -659,9 +640,7 @@ export class CanvasManager {
|
||||
return obj.isBackground || obj.id === backgroundLayerId;
|
||||
});
|
||||
if (!backgroundLayerByBgLayer) {
|
||||
console.warn(
|
||||
"CanvasManager.js = >getBackgroundLayer 方法没有找到背景层"
|
||||
);
|
||||
console.warn("CanvasManager.js = >getBackgroundLayer 方法没有找到背景层");
|
||||
}
|
||||
|
||||
return backgroundLayerByBgLayer;
|
||||
@@ -671,8 +650,7 @@ export class CanvasManager {
|
||||
* @param {Object} backgroundLayerObject 背景层对象
|
||||
*/
|
||||
updateMaskPosition(backgroundLayerObject) {
|
||||
if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath)
|
||||
return;
|
||||
if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath) return;
|
||||
|
||||
const left = backgroundLayerObject.left;
|
||||
const top = backgroundLayerObject.top;
|
||||
@@ -737,8 +715,7 @@ export class CanvasManager {
|
||||
...options,
|
||||
});
|
||||
|
||||
command.undoable =
|
||||
options.undoable !== undefined ? options.undoable : false; // 默认不可撤销 undoable = true 为可撤销
|
||||
command.undoable = options.undoable !== undefined ? options.undoable : false; // 默认不可撤销 undoable = true 为可撤销
|
||||
|
||||
const result = (await command?.execute?.()) || {
|
||||
success: false,
|
||||
@@ -785,9 +762,7 @@ export class CanvasManager {
|
||||
...options,
|
||||
// 如果没有明确指定,则根据当前模式自动设置
|
||||
restoreOpacityInRedGreen:
|
||||
options.restoreOpacityInRedGreen !== undefined
|
||||
? options.restoreOpacityInRedGreen
|
||||
: true, // 默认在红绿图模式下恢复透明度
|
||||
options.restoreOpacityInRedGreen !== undefined ? options.restoreOpacityInRedGreen : true, // 默认在红绿图模式下恢复透明度
|
||||
};
|
||||
|
||||
// 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层
|
||||
@@ -801,9 +776,7 @@ export class CanvasManager {
|
||||
// 获取所有非背景、非固定的普通图层ID
|
||||
const normalLayerIds =
|
||||
this.layers?.value
|
||||
?.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed && layer.visible
|
||||
)
|
||||
?.filter((layer) => !layer.isBackground && !layer.isFixed && layer.visible)
|
||||
?.map((layer) => layer.id) || [];
|
||||
|
||||
if (normalLayerIds.length > 0) {
|
||||
@@ -912,9 +885,11 @@ export class CanvasManager {
|
||||
// });
|
||||
// };
|
||||
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);
|
||||
return JSON.stringify({
|
||||
canvas: this.canvas.toJSON([
|
||||
@@ -962,6 +937,7 @@ export class CanvasManager {
|
||||
this.canvasHeight.value = parsedJson.canvasHeight || this.height;
|
||||
this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor;
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const tempLayers = JSON.parse(parsedJson?.layers) || [];
|
||||
const canvasData = parsedJson?.canvas;
|
||||
@@ -1023,10 +999,7 @@ export class CanvasManager {
|
||||
// 重置画布数据
|
||||
this.setCanvasSize(this.canvas.width, this.canvas.height);
|
||||
// 重新构建对象关系
|
||||
restoreObjectLayerAssociations(
|
||||
this.layers.value,
|
||||
this.canvas.getObjects()
|
||||
);
|
||||
restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects());
|
||||
// 验证图层关联关系 - 稳定后可以注释
|
||||
const isValidate = validateLayerAssociations(
|
||||
this.layers.value,
|
||||
@@ -1038,8 +1011,7 @@ export class CanvasManager {
|
||||
// 使用LayerSort工具重新排列画布对象(如果可用)
|
||||
await this?.layerManager?.layerSort?.rearrangeObjects();
|
||||
|
||||
this.layerManager.activeLayerId.value = this.layers.value[0]
|
||||
.children?.length
|
||||
this.layerManager.activeLayerId.value = this.layers.value[0].children?.length
|
||||
? this.layers.value[0].children[0].id
|
||||
: this.layers.value[0]?.id || parsedJson?.activeLayerId || null;
|
||||
|
||||
@@ -1052,9 +1024,7 @@ export class CanvasManager {
|
||||
await calllBack?.();
|
||||
|
||||
// 确保所有对象的交互性正确设置
|
||||
await this.layerManager?.updateLayersObjectsInteractivity?.(
|
||||
false
|
||||
);
|
||||
await this.layerManager?.updateLayersObjectsInteractivity?.(false);
|
||||
console.log(this.layerManager.layers.value);
|
||||
|
||||
// 更新所有缩略图
|
||||
@@ -1211,9 +1181,7 @@ export class CanvasManager {
|
||||
if (!this.layers || !this.layers.value) return [];
|
||||
|
||||
// 查找所有非背景、非固定的普通图层
|
||||
const normalLayers = this.layers.value.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
);
|
||||
const normalLayers = this.layers.value.filter((layer) => !layer.isBackground && !layer.isFixed);
|
||||
|
||||
// 收集所有普通图层中的对象
|
||||
const objects = [];
|
||||
@@ -1250,15 +1218,9 @@ export class CanvasManager {
|
||||
|
||||
// 比较尺寸(允许5%的误差)
|
||||
const sizeMatch =
|
||||
Math.abs(
|
||||
obj.width * obj.scaleX -
|
||||
fixedLayerObject.width * fixedLayerObject.scaleX
|
||||
) <
|
||||
Math.abs(obj.width * obj.scaleX - fixedLayerObject.width * fixedLayerObject.scaleX) <
|
||||
fixedLayerObject.width * fixedLayerObject.scaleX * 0.05 &&
|
||||
Math.abs(
|
||||
obj.height * obj.scaleY -
|
||||
fixedLayerObject.height * fixedLayerObject.scaleY
|
||||
) <
|
||||
Math.abs(obj.height * obj.scaleY - fixedLayerObject.height * fixedLayerObject.scaleY) <
|
||||
fixedLayerObject.height * fixedLayerObject.scaleY * 0.05;
|
||||
|
||||
// 比较位置(允许一定的偏差)
|
||||
|
||||
@@ -86,12 +86,7 @@ export class ExportManager {
|
||||
* @returns {String} 图片数据URL
|
||||
* @private
|
||||
*/
|
||||
_exportSpecificLayer(
|
||||
layerId,
|
||||
expPicType,
|
||||
isRedGreenMode,
|
||||
restoreOpacityInRedGreen
|
||||
) {
|
||||
_exportSpecificLayer(layerId, expPicType, isRedGreenMode, restoreOpacityInRedGreen) {
|
||||
if (!this.layerManager) {
|
||||
throw new Error("图层管理器未初始化");
|
||||
}
|
||||
@@ -115,19 +110,11 @@ export class ExportManager {
|
||||
|
||||
// 红绿图模式下使用固定尺寸和裁剪
|
||||
if (isRedGreenMode) {
|
||||
return this._exportWithRedGreenMode(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithRedGreenMode(objectsToExport, expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
// 普通模式使用画布尺寸
|
||||
return this._exportWithCanvasSize(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithCanvasSize(objectsToExport, expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,19 +155,11 @@ export class ExportManager {
|
||||
|
||||
// 红绿图模式下使用固定尺寸和裁剪
|
||||
if (isRedGreenMode) {
|
||||
return this._exportWithRedGreenMode(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithRedGreenMode(objectsToExport, expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
// 普通模式使用画布尺寸
|
||||
return this._exportWithCanvasSize(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithCanvasSize(objectsToExport, expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,22 +194,14 @@ export class ExportManager {
|
||||
|
||||
// 红绿图模式下使用固定尺寸和裁剪
|
||||
if (isRedGreenMode) {
|
||||
return this._exportWithRedGreenMode(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithRedGreenMode(objectsToExport, expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
let canvasClipPath = this.canvas.clipPath;
|
||||
if (isCropByBg) {
|
||||
const cropWidth =
|
||||
this.canvasManager?.canvasWidth?.value ||
|
||||
this.canvas?.canvasWidth ||
|
||||
this.canvas.width;
|
||||
this.canvasManager?.canvasWidth?.value || this.canvas?.canvasWidth || this.canvas.width;
|
||||
const cropHeight =
|
||||
this.canvasManager?.canvasHeight?.value ||
|
||||
this.canvas?.canvasHeight ||
|
||||
this.canvas.height;
|
||||
this.canvasManager?.canvasHeight?.value || this.canvas?.canvasHeight || this.canvas.height;
|
||||
canvasClipPath = new fabric.Rect({
|
||||
left: this.canvas.width / 2,
|
||||
top: this.canvas.height / 2,
|
||||
@@ -331,27 +302,14 @@ export class ExportManager {
|
||||
* @returns {String} 图片数据URL
|
||||
* @private
|
||||
*/
|
||||
async _exportObject(
|
||||
obj,
|
||||
expPicType,
|
||||
isRedGreenMode,
|
||||
restoreOpacityInRedGreen
|
||||
) {
|
||||
async _exportObject(obj, expPicType, isRedGreenMode, restoreOpacityInRedGreen) {
|
||||
// 红绿图模式下使用固定尺寸和裁剪
|
||||
if (isRedGreenMode) {
|
||||
return this._exportWithRedGreenMode(
|
||||
[obj],
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithRedGreenMode([obj], expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
// 普通模式使用画布尺寸
|
||||
return this._exportWithCanvasSize(
|
||||
[obj],
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithCanvasSize([obj], expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,8 +332,7 @@ export class ExportManager {
|
||||
if (layerIdArray && !layerIdArray.includes(layer.id)) continue;
|
||||
|
||||
// 检查图层类型过滤条件
|
||||
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed))
|
||||
continue;
|
||||
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed)) continue;
|
||||
|
||||
if (layer.visible) {
|
||||
const layerObjects = this._collectObjectsFromLayer(layer);
|
||||
@@ -514,21 +471,12 @@ export class ExportManager {
|
||||
* @returns {String} 图片数据URL
|
||||
* @private
|
||||
*/
|
||||
async _exportWithRedGreenMode(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
) {
|
||||
async _exportWithRedGreenMode(objectsToExport, expPicType, restoreOpacityInRedGreen) {
|
||||
// 获取固定图层对象(衣服底图)作为参考
|
||||
const fixedLayerObject =
|
||||
this._getFixedLayerObject() ?? this.canvas.clipPath;
|
||||
const fixedLayerObject = this._getFixedLayerObject() ?? this.canvas.clipPath;
|
||||
if (!fixedLayerObject) {
|
||||
console.warn("红绿图模式下未找到固定图层对象,使用画布尺寸");
|
||||
return this._exportWithCanvasSize(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithCanvasSize(objectsToExport, expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
// 获取固定图层对象的边界矩形(包含位置、尺寸、缩放等信息)
|
||||
@@ -565,10 +513,7 @@ export class ExportManager {
|
||||
// 克隆并添加所有对象到临时画布,需要调整位置相对于固定图层
|
||||
for (let i = 0; i < objectsToExport.length; i++) {
|
||||
const obj = objectsToExport[i];
|
||||
const cloned = await this._cloneObjectForExport(
|
||||
obj,
|
||||
restoreOpacityInRedGreen && true
|
||||
);
|
||||
const cloned = await this._cloneObjectForExport(obj, restoreOpacityInRedGreen && true);
|
||||
if (cloned) {
|
||||
// 调整对象位置:将原画布坐标转换为以固定图层为原点的相对坐标
|
||||
cloned.set({
|
||||
@@ -606,12 +551,7 @@ export class ExportManager {
|
||||
* @returns {String} 图片数据URL
|
||||
* @private
|
||||
*/
|
||||
async _exportWithCanvasSize(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen,
|
||||
maskObject
|
||||
) {
|
||||
async _exportWithCanvasSize(objectsToExport, expPicType, restoreOpacityInRedGreen, maskObject) {
|
||||
// 使用当前画布尺寸
|
||||
// const canvasWidth =
|
||||
// this.canvasManager?.canvasWidth?.value || this.canvas.width;
|
||||
@@ -634,44 +574,44 @@ export class ExportManager {
|
||||
console.log("导出图片数据URL:", dataURL);
|
||||
return dataURL;
|
||||
|
||||
// 创建与画布相同尺寸的临时画布
|
||||
const scaleFactor = 2; // 高清导出
|
||||
const tempCanvas = document.createElement("canvas");
|
||||
tempCanvas.width = canvasWidth * scaleFactor;
|
||||
tempCanvas.height = canvasHeight * scaleFactor;
|
||||
tempCanvas.style.width = canvasWidth + "px";
|
||||
tempCanvas.style.height = canvasHeight + "px";
|
||||
// // 创建与画布相同尺寸的临时画布
|
||||
// const scaleFactor = 2; // 高清导出
|
||||
// const tempCanvas = document.createElement("canvas");
|
||||
// tempCanvas.width = canvasWidth * scaleFactor;
|
||||
// tempCanvas.height = canvasHeight * scaleFactor;
|
||||
// tempCanvas.style.width = canvasWidth + "px";
|
||||
// tempCanvas.style.height = canvasHeight + "px";
|
||||
|
||||
const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, {
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
backgroundColor: null,
|
||||
});
|
||||
// const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, {
|
||||
// width: canvasWidth,
|
||||
// height: canvasHeight,
|
||||
// backgroundColor: null,
|
||||
// });
|
||||
|
||||
tempFabricCanvas.enableRetinaScaling = true;
|
||||
tempFabricCanvas.imageSmoothingEnabled = true;
|
||||
tempFabricCanvas.setZoom(1);
|
||||
// tempFabricCanvas.enableRetinaScaling = true;
|
||||
// tempFabricCanvas.imageSmoothingEnabled = true;
|
||||
// tempFabricCanvas.setZoom(1);
|
||||
|
||||
try {
|
||||
// 克隆并添加所有对象到临时画布
|
||||
for (const obj of objectsToExport) {
|
||||
const cloned = await this._cloneObjectForExport(
|
||||
obj,
|
||||
restoreOpacityInRedGreen && false // 普通模式不强制恢复透明度
|
||||
);
|
||||
if (cloned) {
|
||||
tempFabricCanvas.add(cloned);
|
||||
}
|
||||
}
|
||||
// try {
|
||||
// // 克隆并添加所有对象到临时画布
|
||||
// for (const obj of objectsToExport) {
|
||||
// const cloned = await this._cloneObjectForExport(
|
||||
// obj,
|
||||
// restoreOpacityInRedGreen && false, // 普通模式不强制恢复透明度
|
||||
// );
|
||||
// if (cloned) {
|
||||
// tempFabricCanvas.add(cloned);
|
||||
// }
|
||||
// }
|
||||
|
||||
// 渲染画布
|
||||
tempFabricCanvas.renderAll();
|
||||
// // 渲染画布
|
||||
// tempFabricCanvas.renderAll();
|
||||
|
||||
// 生成图片
|
||||
return this._generateHighQualityDataURL(tempCanvas, expPicType);
|
||||
} finally {
|
||||
this._cleanupTempCanvas(tempFabricCanvas);
|
||||
}
|
||||
// // 生成图片
|
||||
// return this._generateHighQualityDataURL(tempCanvas, expPicType);
|
||||
// } finally {
|
||||
// this._cleanupTempCanvas(tempFabricCanvas);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -703,10 +643,7 @@ export class ExportManager {
|
||||
* @returns {Promise<fabric.Object>} 克隆的对象
|
||||
* @private
|
||||
*/
|
||||
_cloneObjectAsync(
|
||||
obj,
|
||||
propertiesToInclude = ["id", "layerId", "layerName", "name"]
|
||||
) {
|
||||
_cloneObjectAsync(obj, propertiesToInclude = ["id", "layerId", "layerName", "name"]) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!obj) {
|
||||
resolve(null);
|
||||
@@ -736,11 +673,7 @@ export class ExportManager {
|
||||
* @returns {Promise<Object>} 克隆的对象
|
||||
* @private
|
||||
*/
|
||||
async _cloneObjectForExport(
|
||||
obj,
|
||||
forceRestoreOpacity = false,
|
||||
removeClipPath = true
|
||||
) {
|
||||
async _cloneObjectForExport(obj, forceRestoreOpacity = false, removeClipPath = true) {
|
||||
if (!obj) return null;
|
||||
|
||||
try {
|
||||
@@ -874,11 +807,7 @@ export class ExportManager {
|
||||
}
|
||||
|
||||
// 克隆对象作为裁剪路径
|
||||
const clonedClipPath = await this._cloneObjectForExport(
|
||||
clipObject,
|
||||
false,
|
||||
false
|
||||
);
|
||||
const clonedClipPath = await this._cloneObjectForExport(clipObject, false, false);
|
||||
|
||||
if (!clonedClipPath) {
|
||||
console.warn("无法克隆裁剪对象");
|
||||
|
||||
@@ -51,24 +51,16 @@ import {
|
||||
} from "../commands/RasterizeLayerCommand";
|
||||
|
||||
// 导入图层排序相关类和混入
|
||||
import {
|
||||
LayerSort,
|
||||
createLayerSort,
|
||||
LayerSortMixin,
|
||||
LayerSortUtils,
|
||||
} from "../utils/LayerSort";
|
||||
import { LayerSort, createLayerSort, LayerSortMixin, LayerSortUtils } from "../utils/LayerSort";
|
||||
|
||||
import CanvasConfig from "../config/canvasConfig";
|
||||
import { isBoolean, template } from "lodash-es";
|
||||
import {
|
||||
findObjectById,
|
||||
generateId,
|
||||
optimizeCanvasRendering,
|
||||
} from "../utils/helper";
|
||||
import { findObjectById, generateId, optimizeCanvasRendering } from "../utils/helper";
|
||||
import { message } from "ant-design-vue";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { getOriginObjectInfo } from "../utils/layerUtils";
|
||||
import { restoreFabricObject } from "../utils/objectHelper";
|
||||
import { UpdateGroupMaskPositionCommand } from "../commands/UpdateGroupMaskPositionCommand";
|
||||
|
||||
/**
|
||||
* 图层管理器 - 负责管理画布上的所有图层
|
||||
@@ -219,11 +211,7 @@ export class LayerManager {
|
||||
// 基于 fabric-with-erasing 库的 erasable 属性设置擦除权限
|
||||
// 只有活动图层、可见、非锁定、非背景、非固定图层的对象才可擦除
|
||||
obj.erasable =
|
||||
isInActiveLayer &&
|
||||
layer.visible &&
|
||||
!layer.locked &&
|
||||
!layer.isBackground &&
|
||||
!layer.isFixed;
|
||||
isInActiveLayer && layer.visible && !layer.locked && !layer.isBackground && !layer.isFixed;
|
||||
|
||||
// 图层状态决定交互性
|
||||
if (layer.isBackground || obj.isBackground || layer.isFixed) {
|
||||
@@ -270,11 +258,7 @@ export class LayerManager {
|
||||
|
||||
if (this.isRedGreenMode) {
|
||||
// 红绿图模式下 所有普通图层都可擦除
|
||||
obj.erasable =
|
||||
layer.visible &&
|
||||
!layer.locked &&
|
||||
!layer.isBackground &&
|
||||
!layer.isFixed;
|
||||
obj.erasable = layer.visible && !layer.locked && !layer.isBackground && !layer.isFixed;
|
||||
}
|
||||
|
||||
// 平移模式下,禁用多选和擦除
|
||||
@@ -325,9 +309,7 @@ export class LayerManager {
|
||||
|
||||
// 设置子图层对象的交互性
|
||||
layer?.childLayer?.forEach(async (childLayer) => {
|
||||
const childObj = this.canvas
|
||||
.getObjects()
|
||||
.find((o) => o.layerId === childLayer.id);
|
||||
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
|
||||
if (childObj) {
|
||||
await this._setObjectInteractivity(childObj, childLayer, editorMode);
|
||||
}
|
||||
@@ -339,10 +321,7 @@ export class LayerManager {
|
||||
let clippingMaskFabricObject = null;
|
||||
if (layer.clippingMask) {
|
||||
// 反序列化 clippingMask
|
||||
clippingMaskFabricObject = await restoreFabricObject(
|
||||
layer.clippingMask,
|
||||
this.canvas
|
||||
);
|
||||
clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas);
|
||||
clippingMaskFabricObject.clipPath = null;
|
||||
|
||||
clippingMaskFabricObject.set({
|
||||
@@ -354,9 +333,7 @@ export class LayerManager {
|
||||
// 如果是组图层 则给所有子对象设置裁剪对象
|
||||
if (layer.type === LayerType.GROUP || layer.children?.length > 0) {
|
||||
layer.children.forEach((childLayer) => {
|
||||
const childObj = this.canvas
|
||||
.getObjects()
|
||||
.find((o) => o.layerId === childLayer.id);
|
||||
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
|
||||
if (childObj) {
|
||||
childObj.clipPath = clippingMaskFabricObject;
|
||||
childObj.dirty = true; // 标记为脏对象
|
||||
@@ -365,9 +342,7 @@ export class LayerManager {
|
||||
});
|
||||
} else {
|
||||
layer.fabricObjects?.forEach((obj) => {
|
||||
const fabricObject = this.canvas
|
||||
.getObjects()
|
||||
.find((o) => o.id === obj.id);
|
||||
const fabricObject = this.canvas.getObjects().find((o) => o.id === obj.id);
|
||||
if (fabricObject) {
|
||||
fabricObject.clipPath = clippingMaskFabricObject;
|
||||
fabricObject.dirty = true; // 标记为脏对象
|
||||
@@ -447,9 +422,7 @@ export class LayerManager {
|
||||
*/
|
||||
createBackgroundLayer(name = "背景") {
|
||||
// 检查是否已有背景图层
|
||||
const hasBackgroundLayer = this.layers.value.some(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const hasBackgroundLayer = this.layers.value.some((layer) => layer.isBackground);
|
||||
|
||||
if (hasBackgroundLayer) {
|
||||
console.warn("已存在背景层,不再创建新的背景层");
|
||||
@@ -502,9 +475,7 @@ export class LayerManager {
|
||||
}
|
||||
|
||||
// 生成唯一ID
|
||||
const layerId = `fixed_layer_${Date.now()}_${Math.floor(
|
||||
Math.random() * 1000
|
||||
)}`;
|
||||
const layerId = `fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
|
||||
// 创建固定图层
|
||||
const fixedLayer = createFixedLayer({
|
||||
@@ -564,9 +535,7 @@ export class LayerManager {
|
||||
await this.createLayer("图层 1");
|
||||
} else {
|
||||
// 检查是否已有背景层
|
||||
const hasBackgroundLayer = this.layers.value.some(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const hasBackgroundLayer = this.layers.value.some((layer) => layer.isBackground);
|
||||
|
||||
if (!hasBackgroundLayer) {
|
||||
this.createBackgroundLayer();
|
||||
@@ -612,10 +581,7 @@ export class LayerManager {
|
||||
}
|
||||
|
||||
// 验证目标图层是否存在
|
||||
const { layer: targetLayer } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
targetLayerId
|
||||
);
|
||||
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
|
||||
if (!targetLayer) {
|
||||
console.error(`目标图层 ${targetLayerId} 不存在`);
|
||||
return null;
|
||||
@@ -669,8 +635,7 @@ export class LayerManager {
|
||||
*/
|
||||
removeObjectFromLayer(objectOrId) {
|
||||
// 获取对象ID
|
||||
const objectId =
|
||||
typeof objectOrId === "string" ? objectOrId : objectOrId.id;
|
||||
const objectId = typeof objectOrId === "string" ? objectOrId : objectOrId.id;
|
||||
|
||||
if (!objectId) {
|
||||
console.error("无效的对象ID");
|
||||
@@ -729,9 +694,7 @@ export class LayerManager {
|
||||
*/
|
||||
getActiveLayer() {
|
||||
if (!this.activeLayerId.value) {
|
||||
console.warn(
|
||||
"没有活动图层ID,无法获取活动图层 ==== 默认设置第一个图层为活动图层"
|
||||
);
|
||||
console.warn("没有活动图层ID,无法获取活动图层 ==== 默认设置第一个图层为活动图层");
|
||||
this.activeLayerId.value = this.layers.value[0]?.id || null;
|
||||
}
|
||||
|
||||
@@ -836,13 +799,8 @@ export class LayerManager {
|
||||
}
|
||||
|
||||
const tempFabricObject =
|
||||
fabricObject?.toObject?.([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"isBackgroud",
|
||||
"isFixed",
|
||||
]) || fabricObject;
|
||||
fabricObject?.toObject?.(["id", "layerId", "layerName", "isBackgroud", "isFixed"]) ||
|
||||
fabricObject;
|
||||
if (layer.isFixed || layer.isBackground) {
|
||||
layer.fabricObject = tempFabricObject;
|
||||
} else {
|
||||
@@ -868,9 +826,7 @@ export class LayerManager {
|
||||
|
||||
// 如果是背景层或固定层,不允许移动
|
||||
if (layer && (layer.isBackground || layer.isFixed)) {
|
||||
console.warn(
|
||||
layer.isBackground ? $t("背景层不可移动") : $t("固定层不可移动")
|
||||
);
|
||||
console.warn(layer.isBackground ? $t("背景层不可移动") : $t("固定层不可移动"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1000,6 +956,9 @@ export class LayerManager {
|
||||
|
||||
// 设置活动选择组的属性
|
||||
this.canvas.setActiveObject(activeSelection);
|
||||
// 为活动选择组添加移动事件监听器,用于同步更新遮罩位置
|
||||
this._setupGroupMaskMovementSync(activeSelection, layer);
|
||||
|
||||
activeSelection = null; // 清理引用,避免内存泄漏
|
||||
// 确保选择组正确渲染
|
||||
// activeSelection.setCoords();
|
||||
@@ -1234,11 +1193,7 @@ export class LayerManager {
|
||||
async ungroupLayers(groupId) {
|
||||
// 查找组图层
|
||||
const groupLayer = this.layers.value.find((l) => l.id === groupId);
|
||||
if (
|
||||
!groupLayer ||
|
||||
!groupLayer.children ||
|
||||
groupLayer.children.length === 0
|
||||
) {
|
||||
if (!groupLayer || !groupLayer.children || groupLayer.children.length === 0) {
|
||||
console.error(`${groupId} 不是有效的组图层或不包含子图层`);
|
||||
return null;
|
||||
}
|
||||
@@ -1295,9 +1250,7 @@ export class LayerManager {
|
||||
*/
|
||||
resizeCanvasWithScale(width, height, options = {}) {
|
||||
// 检查是否有除背景层外的其他元素
|
||||
const hasOtherElements = this.canvas
|
||||
.getObjects()
|
||||
.some((obj) => !obj.isBackground);
|
||||
const hasOtherElements = this.canvas.getObjects().some((obj) => !obj.isBackground);
|
||||
|
||||
if (hasOtherElements) {
|
||||
// 有其他元素时使用带缩放的命令
|
||||
@@ -1366,9 +1319,7 @@ export class LayerManager {
|
||||
if (!this.canvas) return;
|
||||
|
||||
// 获取画布上的所有对象
|
||||
const canvasObjects = [
|
||||
...this.canvas.getObjects(["id", "layerId", "layerName"]),
|
||||
];
|
||||
const canvasObjects = [...this.canvas.getObjects(["id", "layerId", "layerName"])];
|
||||
|
||||
// 清空画布
|
||||
this.canvas.clear();
|
||||
@@ -1383,19 +1334,13 @@ export class LayerManager {
|
||||
|
||||
if (layer.isBackground && layer.fabricObject) {
|
||||
// 背景图层
|
||||
const originalObj = canvasObjects.find(
|
||||
(o) => o.id === layer.fabricObject.id
|
||||
);
|
||||
const originalObj = canvasObjects.find((o) => o.id === layer.fabricObject.id);
|
||||
if (originalObj) {
|
||||
this.canvas.add(originalObj);
|
||||
} else {
|
||||
this.canvas.add(layer.fabricObject);
|
||||
}
|
||||
} else if (
|
||||
layer.isFixed &&
|
||||
layer.fabricObjects &&
|
||||
layer.fabricObjects.length > 0
|
||||
) {
|
||||
} else if (layer.isFixed && layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
// 固定图层
|
||||
layer.fabricObjects.forEach((obj) => {
|
||||
const originalObj = canvasObjects.find((o) => o.id === obj.id);
|
||||
@@ -1405,10 +1350,7 @@ export class LayerManager {
|
||||
this.canvas.add(obj);
|
||||
}
|
||||
});
|
||||
} else if (
|
||||
Array.isArray(layer.fabricObjects) &&
|
||||
layer.fabricObjects.length > 0
|
||||
) {
|
||||
} else if (Array.isArray(layer.fabricObjects) && layer.fabricObjects.length > 0) {
|
||||
// 普通图层,添加所有fabricObjects
|
||||
layer.fabricObjects.forEach((obj) => {
|
||||
const originalObj = canvasObjects.find((o) => o.id === obj.id);
|
||||
@@ -1437,9 +1379,7 @@ export class LayerManager {
|
||||
if (layer.isBackground) {
|
||||
// 背景图层处理
|
||||
if (layer.fabricObject) {
|
||||
const existsOnCanvas = canvasObjects.some(
|
||||
(obj) => obj.id === layer.fabricObject.id
|
||||
);
|
||||
const existsOnCanvas = canvasObjects.some((obj) => obj.id === layer.fabricObject.id);
|
||||
if (!existsOnCanvas) {
|
||||
this.canvas.add(layer.fabricObject);
|
||||
}
|
||||
@@ -1549,9 +1489,7 @@ export class LayerManager {
|
||||
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
layerCopy.serializedObjects = layer.fabricObjects
|
||||
.map((obj) =>
|
||||
typeof obj.toObject === "function"
|
||||
? obj.toObject(["id", "layerId", "layerName"])
|
||||
: null
|
||||
typeof obj.toObject === "function" ? obj.toObject(["id", "layerId", "layerName"]) : null
|
||||
)
|
||||
.filter(Boolean);
|
||||
}
|
||||
@@ -1596,9 +1534,7 @@ export class LayerManager {
|
||||
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
layerCopy.serializedObjects = layer.fabricObjects
|
||||
.map((obj) =>
|
||||
typeof obj.toObject === "function"
|
||||
? obj.toObject(["id", "layerId", "layerName"])
|
||||
: null
|
||||
typeof obj.toObject === "function" ? obj.toObject(["id", "layerId", "layerName"]) : null
|
||||
)
|
||||
.filter(Boolean);
|
||||
}
|
||||
@@ -1719,8 +1655,7 @@ export class LayerManager {
|
||||
// 查找第一个非背景、非锁定的图层,排除指定的图层
|
||||
return (
|
||||
this.layers.value.find(
|
||||
(layer) =>
|
||||
layer.id !== excludeLayerId && !layer.isBackground && !layer.locked
|
||||
(layer) => layer.id !== excludeLayerId && !layer.isBackground && !layer.locked
|
||||
) || null
|
||||
);
|
||||
}
|
||||
@@ -1855,9 +1790,7 @@ export class LayerManager {
|
||||
* @param {string} backgroundColor 背景颜色
|
||||
*/
|
||||
updateBackgroundColor(backgroundColor, options = {}) {
|
||||
const backgroundLayer = this.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const backgroundLayer = this.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
if (!backgroundLayer) {
|
||||
console.warn("没有找到背景图层");
|
||||
@@ -2057,8 +1990,7 @@ export class LayerManager {
|
||||
if (!this.canvas || !textObject) return null;
|
||||
|
||||
// 确保对象有ID
|
||||
textObject.id =
|
||||
textObject.id || `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
textObject.id = textObject.id || `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
|
||||
// 创建文本图层
|
||||
const layerName = options.name || "文本图层";
|
||||
@@ -2075,9 +2007,7 @@ export class LayerManager {
|
||||
overline: options.overline || textObject.overline || false,
|
||||
fill: options.fill || textObject.fill || "#000000",
|
||||
textBackgroundColor:
|
||||
options.textBackgroundColor ||
|
||||
textObject.textBackgroundColor ||
|
||||
"transparent",
|
||||
options.textBackgroundColor || textObject.textBackgroundColor || "transparent",
|
||||
lineHeight: options.lineHeight || textObject.lineHeight || 1.16,
|
||||
charSpacing: options.charSpacing || textObject.charSpacing || 0,
|
||||
},
|
||||
@@ -2088,9 +2018,7 @@ export class LayerManager {
|
||||
textObject.layerName = layerName;
|
||||
|
||||
// 添加到画布,如果还未添加
|
||||
const isOnCanvas = this.canvas
|
||||
.getObjects()
|
||||
.some((obj) => obj.id === textObject.id);
|
||||
const isOnCanvas = this.canvas.getObjects().some((obj) => obj.id === textObject.id);
|
||||
if (!isOnCanvas) {
|
||||
this.canvas.add(textObject);
|
||||
}
|
||||
@@ -2099,9 +2027,7 @@ export class LayerManager {
|
||||
const layer = this.getLayerById(layerId);
|
||||
if (layer) {
|
||||
layer.fabricObjects = layer.fabricObjects || [];
|
||||
layer.fabricObjects.push(
|
||||
textObject.toObject(["id", "layerId", "layerName"])
|
||||
);
|
||||
layer.fabricObjects.push(textObject.toObject(["id", "layerId", "layerName"]));
|
||||
}
|
||||
|
||||
// 设置此图层为活动图层
|
||||
@@ -2134,9 +2060,7 @@ export class LayerManager {
|
||||
|
||||
// 检查普通图层
|
||||
if (layer.fabricObjects && Array.isArray(layer.fabricObjects)) {
|
||||
const foundObject = layer.fabricObjects.find(
|
||||
(obj) => obj.id === fabricObject.id
|
||||
);
|
||||
const foundObject = layer.fabricObjects.find((obj) => obj.id === fabricObject.id);
|
||||
if (foundObject) {
|
||||
return layer;
|
||||
}
|
||||
@@ -2217,12 +2141,7 @@ export class LayerManager {
|
||||
* @returns {boolean} 是否排序成功
|
||||
*/
|
||||
reorderChildLayers(parentId, oldIndex, newIndex, layerId) {
|
||||
return this.layerSort?.reorderChildLayers(
|
||||
parentId,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
layerId
|
||||
);
|
||||
return this.layerSort?.reorderChildLayers(parentId, oldIndex, newIndex, layerId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2403,19 +2322,13 @@ export class LayerManager {
|
||||
const layersCount = this.layers?.value?.length || 0;
|
||||
|
||||
const shouldUseAsync =
|
||||
async !== null
|
||||
? async
|
||||
: LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount);
|
||||
async !== null ? async : LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount);
|
||||
|
||||
if (shouldUseAsync) {
|
||||
console.log(
|
||||
`使用异步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`
|
||||
);
|
||||
console.log(`使用异步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`);
|
||||
return this.layerSort.rearrangeObjectsAsync();
|
||||
} else {
|
||||
console.log(
|
||||
`使用同步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`
|
||||
);
|
||||
console.log(`使用同步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`);
|
||||
this.layerSort.rearrangeObjects();
|
||||
}
|
||||
}
|
||||
@@ -2524,10 +2437,7 @@ export class LayerManager {
|
||||
moveLayerToIndex({ parentId, oldIndex, newIndex, layerId }) {
|
||||
if (!this.layerSort) {
|
||||
console.warn("图层排序工具未初始化,使用基础移动方法");
|
||||
return this.moveLayer(
|
||||
layerId,
|
||||
newIndex > this.getLayerIndex(layerId) ? "down" : "up"
|
||||
);
|
||||
return this.moveLayer(layerId, newIndex > this.getLayerIndex(layerId) ? "down" : "up");
|
||||
}
|
||||
|
||||
const result = this.layerSort.moveLayerToIndex({
|
||||
@@ -2538,9 +2448,7 @@ export class LayerManager {
|
||||
});
|
||||
|
||||
if (result) {
|
||||
console.log(
|
||||
`图层 ${layerId} - oldIndex: ${oldIndex} 已移动到位置 ${newIndex}`
|
||||
);
|
||||
console.log(`图层 ${layerId} - oldIndex: ${oldIndex} 已移动到位置 ${newIndex}`);
|
||||
// 更新对象交互性
|
||||
// this.updateLayersObjectsInteractivity();
|
||||
}
|
||||
@@ -2584,9 +2492,7 @@ export class LayerManager {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let layerIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.id === layerId
|
||||
);
|
||||
let layerIndex = this.layers.value.findIndex((layer) => layer.id === layerId);
|
||||
if (layerIndex >= 0) return layerIndex;
|
||||
// 如果未找到,尝试在子图层中查找
|
||||
const { parent } = findLayerRecursively(this.layers.value, layerId);
|
||||
@@ -2617,9 +2523,7 @@ export class LayerManager {
|
||||
const result = this.layerSort.reorderLayers(oldIndex, newIndex, layerId);
|
||||
|
||||
if (result) {
|
||||
console.log(
|
||||
`高级排序完成: 图层 ${layerId} 从位置 ${oldIndex} 移动到 ${newIndex}`
|
||||
);
|
||||
console.log(`高级排序完成: 图层 ${layerId} 从位置 ${oldIndex} 移动到 ${newIndex}`);
|
||||
// 更新对象交互性
|
||||
this.updateLayersObjectsInteractivity();
|
||||
}
|
||||
@@ -2636,12 +2540,7 @@ export class LayerManager {
|
||||
* @returns {boolean} 是否排序成功
|
||||
*/
|
||||
advancedReorderChildLayers(parentId, oldIndex, newIndex, layerId) {
|
||||
const result = this.reorderChildLayers(
|
||||
parentId,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
layerId
|
||||
);
|
||||
const result = this.reorderChildLayers(parentId, oldIndex, newIndex, layerId);
|
||||
|
||||
if (result) {
|
||||
console.log(
|
||||
@@ -2758,11 +2657,7 @@ export class LayerManager {
|
||||
async mergeGroupLayers(groupId) {
|
||||
// 查找组图层
|
||||
const groupLayer = this.layers.value.find((l) => l.id === groupId);
|
||||
if (
|
||||
!groupLayer ||
|
||||
!groupLayer.children ||
|
||||
groupLayer.children.length === 0
|
||||
) {
|
||||
if (!groupLayer || !groupLayer.children || groupLayer.children.length === 0) {
|
||||
console.warn($t("找不到有效的组图层或组图层为空"));
|
||||
return [];
|
||||
}
|
||||
@@ -2803,10 +2698,7 @@ export class LayerManager {
|
||||
|
||||
// 查找目标图层
|
||||
// const targetLayer = this.getLayerById(targetLayerId);
|
||||
const { layer: targetLayer } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
targetLayerId
|
||||
);
|
||||
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
|
||||
|
||||
if (!targetLayer) {
|
||||
console.error($t("图层不存在", { layerId: targetLayerId }));
|
||||
@@ -2864,9 +2756,7 @@ export class LayerManager {
|
||||
return (
|
||||
layer.children &&
|
||||
layer.children.length > 0 &&
|
||||
layer.children.some(
|
||||
(child) => child.fabricObjects && child.fabricObjects.length > 0
|
||||
)
|
||||
layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0)
|
||||
);
|
||||
} else {
|
||||
// 普通图层:检查是否有对象
|
||||
@@ -2888,10 +2778,7 @@ export class LayerManager {
|
||||
|
||||
// 查找目标图层
|
||||
// const targetLayer = this.getLayerById(targetLayerId);
|
||||
const { layer: targetLayer } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
targetLayerId
|
||||
);
|
||||
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
|
||||
|
||||
if (!targetLayer) {
|
||||
console.error($t("图层不存在", { layerId: targetLayerId }));
|
||||
@@ -2926,15 +2813,162 @@ export class LayerManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新图层缩略图
|
||||
* @param {string} layerId 图层ID
|
||||
* 为组图层的活动选择组设置遮罩移动同步(修复版)
|
||||
* @param {fabric.ActiveSelection} activeSelection 活动选择组
|
||||
* @param {Object} layer 组图层对象
|
||||
* @private
|
||||
*/
|
||||
_updateLayerThumbnail(layerId) {
|
||||
if (this.canvas && this.canvas.thumbnailManager) {
|
||||
setTimeout(() => {
|
||||
this.canvas.thumbnailManager.generateLayerThumbnail(layerId);
|
||||
}, 100);
|
||||
_setupGroupMaskMovementSync(activeSelection, layer) {
|
||||
if (!activeSelection || !layer || !layer.clippingMask) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 记录初始位置
|
||||
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 事件");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +141,7 @@ export class LiquifyReferenceManager {
|
||||
const snapshot = this.stateSnapshots.get(snapshotId);
|
||||
|
||||
if (!fabricObject || !snapshot) {
|
||||
throw new Error(
|
||||
`无法恢复快照: 对象或快照不存在 (${refId}, ${snapshotId})`
|
||||
);
|
||||
throw new Error(`无法恢复快照: 对象或快照不存在 (${refId}, ${snapshotId})`);
|
||||
}
|
||||
|
||||
// 恢复图像数据
|
||||
@@ -175,10 +173,7 @@ export class LiquifyReferenceManager {
|
||||
|
||||
for (const update of updates) {
|
||||
try {
|
||||
const result = await this.updateObjectImageData(
|
||||
update.refId,
|
||||
update.imageData
|
||||
);
|
||||
const result = await this.updateObjectImageData(update.refId, update.imageData);
|
||||
results.push({ refId: update.refId, success: true, result });
|
||||
} catch (error) {
|
||||
console.error(`批量更新失败 ${update.refId}:`, error);
|
||||
@@ -253,19 +248,10 @@ export class LiquifyReferenceManager {
|
||||
const listeners = {};
|
||||
|
||||
// 备份常见的事件监听器
|
||||
const eventTypes = [
|
||||
"mousedown",
|
||||
"mouseup",
|
||||
"mousemove",
|
||||
"mouseout",
|
||||
"mouseover",
|
||||
];
|
||||
const eventTypes = ["mousedown", "mouseup", "mousemove", "mouseout", "mouseover"];
|
||||
|
||||
eventTypes.forEach((eventType) => {
|
||||
if (
|
||||
fabricObject.__eventListeners &&
|
||||
fabricObject.__eventListeners[eventType]
|
||||
) {
|
||||
if (fabricObject.__eventListeners && fabricObject.__eventListeners[eventType]) {
|
||||
listeners[eventType] = [...fabricObject.__eventListeners[eventType]];
|
||||
}
|
||||
});
|
||||
@@ -378,11 +364,7 @@ export class LiquifyReferenceManager {
|
||||
*/
|
||||
_captureImageData(fabricObject) {
|
||||
try {
|
||||
if (
|
||||
fabricObject._element &&
|
||||
fabricObject._element.width &&
|
||||
fabricObject._element.height
|
||||
) {
|
||||
if (fabricObject._element && fabricObject._element.width && fabricObject._element.height) {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = fabricObject._element.width;
|
||||
canvas.height = fabricObject._element.height;
|
||||
|
||||
@@ -55,14 +55,10 @@ export class RedGreenModeManager {
|
||||
if (typeof options.normalLayerOpacity === "number") {
|
||||
if (options.normalLayerOpacity > 1) {
|
||||
// 如果大于1,认为是百分比值(0-100)
|
||||
this.normalLayerOpacity =
|
||||
Math.max(0, Math.min(100, options.normalLayerOpacity)) / 100;
|
||||
this.normalLayerOpacity = Math.max(0, Math.min(100, options.normalLayerOpacity)) / 100;
|
||||
} else {
|
||||
// 如果小于等于1,认为是小数值(0-1)
|
||||
this.normalLayerOpacity = Math.max(
|
||||
0,
|
||||
Math.min(1, options.normalLayerOpacity)
|
||||
);
|
||||
this.normalLayerOpacity = Math.max(0, Math.min(1, options.normalLayerOpacity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,10 +93,7 @@ export class RedGreenModeManager {
|
||||
this.registerRedGreenMouseUpEvent();
|
||||
|
||||
// 启用图层管理器的红绿图模式
|
||||
if (
|
||||
this.layerManager &&
|
||||
typeof this.layerManager.enableRedGreenMode === "function"
|
||||
) {
|
||||
if (this.layerManager && typeof this.layerManager.enableRedGreenMode === "function") {
|
||||
this.layerManager.enableRedGreenMode();
|
||||
}
|
||||
|
||||
@@ -177,26 +170,24 @@ export class RedGreenModeManager {
|
||||
normalizedOpacity = Math.max(0, Math.min(1, opacity));
|
||||
}
|
||||
|
||||
// 创建透明度更新命令
|
||||
const opacityCommand = new UpdateNormalLayerOpacityCommand({
|
||||
canvas: this.canvas,
|
||||
layerManager: this.layerManager,
|
||||
opacity: normalizedOpacity,
|
||||
});
|
||||
// // 创建透明度更新命令
|
||||
// const opacityCommand = new UpdateNormalLayerOpacityCommand({
|
||||
// canvas: this.canvas,
|
||||
// layerManager: this.layerManager,
|
||||
// opacity: normalizedOpacity,
|
||||
// });
|
||||
|
||||
// 执行命令
|
||||
if (this.commandManager) {
|
||||
this.commandManager.execute(opacityCommand);
|
||||
} else {
|
||||
opacityCommand.execute();
|
||||
}
|
||||
// // 执行命令
|
||||
// if (this.commandManager) {
|
||||
// this.commandManager.execute(opacityCommand);
|
||||
// } else {
|
||||
// opacityCommand.execute();
|
||||
// }
|
||||
|
||||
// 更新内部状态
|
||||
this.normalLayerOpacity = normalizedOpacity;
|
||||
|
||||
console.log(
|
||||
`普通图层透明度已更新为: ${Math.round(normalizedOpacity * 100)}%`
|
||||
);
|
||||
console.log(`普通图层透明度已更新为: ${Math.round(normalizedOpacity * 100)}%`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("更新普通图层透明度失败:", error);
|
||||
@@ -280,9 +271,7 @@ export class RedGreenModeManager {
|
||||
const layers = this.layerManager.layers.value || [];
|
||||
const backgroundLayer = layers.find((layer) => layer.isBackground);
|
||||
const fixedLayer = layers.find((layer) => layer.isFixed);
|
||||
const normalLayers = layers.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
);
|
||||
const normalLayers = layers.filter((layer) => !layer.isBackground && !layer.isFixed);
|
||||
|
||||
return {
|
||||
backgroundLayer:
|
||||
@@ -318,10 +307,7 @@ export class RedGreenModeManager {
|
||||
cleanup() {
|
||||
try {
|
||||
// 禁用图层管理器的红绿图模式
|
||||
if (
|
||||
this.layerManager &&
|
||||
typeof this.layerManager.disableRedGreenMode === "function"
|
||||
) {
|
||||
if (this.layerManager && typeof this.layerManager.disableRedGreenMode === "function") {
|
||||
this.layerManager.disableRedGreenMode();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ export class ThumbnailManager {
|
||||
this.layers = options.layers || []; // 图层管理器
|
||||
this.layerThumbSize = options.layerThumbSize || { width: 48, height: 48 };
|
||||
|
||||
this.layerThumbnails = new Map(); // 图层缩略图缓存
|
||||
// this.layerThumbnails = new Map(); // 图层缩略图缓存 - 改成使用图层对象的thumbnailUrl属性
|
||||
// 使用图层对象的thumbnailUrl属性来存储缩略图URL
|
||||
this.defaultThumbnail =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="; // 1x1 透明图
|
||||
}
|
||||
@@ -23,17 +24,24 @@ export class ThumbnailManager {
|
||||
async generateLayerThumbnail(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
|
||||
fabricObjects.length > 0 &&
|
||||
requestIdleCallback(() => {
|
||||
setTimeout(async () => {
|
||||
const base64 = await this._generateLayerThumbnailNow(fabricObjects);
|
||||
this.layerThumbnails.set(layerId, base64);
|
||||
// this.layerThumbnails.set(layerId, base64);
|
||||
try {
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
layerId
|
||||
);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, layerId);
|
||||
if (layer) {
|
||||
layer.thumbnailUrl = base64; // 更新图层对象的缩略图
|
||||
}
|
||||
@@ -201,7 +209,7 @@ export class ThumbnailManager {
|
||||
*/
|
||||
getLayerThumbnail(layerId) {
|
||||
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) {
|
||||
if (layerId && this.layerThumbnails.has(layerId)) {
|
||||
this.layerThumbnails.delete(layerId);
|
||||
// this.layerThumbnails.delete(layerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +226,7 @@ export class ThumbnailManager {
|
||||
* 清除所有缩略图
|
||||
*/
|
||||
clearAllThumbnails() {
|
||||
this.layerThumbnails.clear();
|
||||
// this.layerThumbnails.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -258,10 +258,7 @@ export class ToolManager {
|
||||
const shiftKey = event.shiftKey;
|
||||
|
||||
// 当处于输入状态时不触发快捷键
|
||||
if (
|
||||
event.target.tagName === "INPUT" ||
|
||||
event.target.tagName === "TEXTAREA"
|
||||
) {
|
||||
if (event.target.tagName === "INPUT" || event.target.tagName === "TEXTAREA") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -286,10 +283,7 @@ export class ToolManager {
|
||||
}
|
||||
|
||||
// 在红绿图模式下检查工具可用性
|
||||
if (
|
||||
this.isRedGreenMode &&
|
||||
!this.isToolAvailableInRedGreenMode(toolId)
|
||||
) {
|
||||
if (this.isRedGreenMode && !this.isToolAvailableInRedGreenMode(toolId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -564,9 +558,7 @@ export class ToolManager {
|
||||
|
||||
// 通知选区管理器切换到矩形套索工具
|
||||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||||
this.canvasManager.selectionManager.setCurrentTool(
|
||||
OperationType.LASSO_RECTANGLE
|
||||
);
|
||||
this.canvasManager.selectionManager.setCurrentTool(OperationType.LASSO_RECTANGLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,9 +573,7 @@ export class ToolManager {
|
||||
|
||||
// 通知选区管理器切换到椭圆套索工具
|
||||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||||
this.canvasManager.selectionManager.setCurrentTool(
|
||||
OperationType.LASSO_ELLIPSE
|
||||
);
|
||||
this.canvasManager.selectionManager.setCurrentTool(OperationType.LASSO_ELLIPSE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,9 +588,7 @@ export class ToolManager {
|
||||
|
||||
// 通知选区管理器切换到椭圆套索工具
|
||||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||||
this.canvasManager.selectionManager.setCurrentTool(
|
||||
OperationType.AREA_CUSTOM
|
||||
);
|
||||
this.canvasManager.selectionManager.setCurrentTool(OperationType.AREA_CUSTOM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,9 +606,7 @@ export class ToolManager {
|
||||
console.log("矩形选区工具已激活");
|
||||
|
||||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||||
this.canvasManager.selectionManager.setCurrentTool(
|
||||
OperationType.AREA_RECTANGLE
|
||||
);
|
||||
this.canvasManager.selectionManager.setCurrentTool(OperationType.AREA_RECTANGLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,10 +663,7 @@ export class ToolManager {
|
||||
if (layer) {
|
||||
if (layer.isBackground || layer.type === "background") {
|
||||
panelDetail.targetObject = layer.fabricObject;
|
||||
} else if (
|
||||
layer.fabricObjects &&
|
||||
layer.fabricObjects.length > 0
|
||||
) {
|
||||
} else if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
panelDetail.targetObject = layer.fabricObjects[0];
|
||||
}
|
||||
|
||||
@@ -906,9 +889,7 @@ export class ToolManager {
|
||||
});
|
||||
|
||||
// 准备液化操作,获取原始图像数据
|
||||
const prepareResult = await liquifyManager.prepareForLiquify(
|
||||
targetObject
|
||||
);
|
||||
const prepareResult = await liquifyManager.prepareForLiquify(targetObject);
|
||||
|
||||
// 创建和初始化命令
|
||||
const initCommand = new InitLiquifyToolCommand({
|
||||
@@ -1110,9 +1091,7 @@ export class ToolManager {
|
||||
const target = e.target;
|
||||
if (
|
||||
target &&
|
||||
(target.type === "text" ||
|
||||
target.type === "i-text" ||
|
||||
target.type === "textbox")
|
||||
(target.type === "text" || target.type === "i-text" || target.type === "textbox")
|
||||
) {
|
||||
// 获取对应的图层
|
||||
const layer = this.layerManager.getLayerById(target.layerId);
|
||||
@@ -1380,11 +1359,7 @@ export class ToolManager {
|
||||
}
|
||||
|
||||
// 从画布笔刷获取
|
||||
if (
|
||||
this.canvas &&
|
||||
this.canvas.freeDrawingBrush &&
|
||||
this.canvas.freeDrawingBrush.width
|
||||
) {
|
||||
if (this.canvas && this.canvas.freeDrawingBrush && this.canvas.freeDrawingBrush.width) {
|
||||
return this.canvas.freeDrawingBrush.width;
|
||||
}
|
||||
|
||||
@@ -1409,11 +1384,7 @@ export class ToolManager {
|
||||
}
|
||||
|
||||
// 从画布笔刷获取
|
||||
if (
|
||||
this.canvas &&
|
||||
this.canvas.freeDrawingBrush &&
|
||||
this.canvas.freeDrawingBrush.color
|
||||
) {
|
||||
if (this.canvas && this.canvas.freeDrawingBrush && this.canvas.freeDrawingBrush.color) {
|
||||
return this.canvas.freeDrawingBrush.color;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,8 +87,7 @@ export class AnimationManager {
|
||||
|
||||
// 计算过渡动画持续时间 - 根据当前值到目标值的距离比例
|
||||
const progressRatio =
|
||||
Math.abs(targetZoom - currentZoomValue) /
|
||||
Math.abs(targetZoom - currentZoom);
|
||||
Math.abs(targetZoom - currentZoomValue) / Math.abs(targetZoom - currentZoom);
|
||||
const duration = options.duration || 0.3 * progressRatio;
|
||||
|
||||
// 计算缩放后目标位置需要的修正,保持缩放点不变
|
||||
@@ -425,17 +424,17 @@ export class AnimationManager {
|
||||
const now = Date.now();
|
||||
|
||||
// Mac设备的轻量防抖检查 - 进一步减少冷却时间,确保响应性
|
||||
if (
|
||||
this._isMac &&
|
||||
now - this._lastMacAnimationTime < this._macAnimationCooldown
|
||||
) {
|
||||
if (this._isMac && now - this._lastMacAnimationTime < this._macAnimationCooldown) {
|
||||
// 如果距离上次动画时间太短,只延迟很短时间,不阻塞太久
|
||||
if (this._wheelAccumulationTimeout) {
|
||||
clearTimeout(this._wheelAccumulationTimeout);
|
||||
}
|
||||
this._wheelAccumulationTimeout = setTimeout(() => {
|
||||
this._processAccumulatedWheel(lastOpt);
|
||||
}, Math.min(this._macAnimationCooldown, 3)); // 最多延迟3ms
|
||||
this._wheelAccumulationTimeout = setTimeout(
|
||||
() => {
|
||||
this._processAccumulatedWheel(lastOpt);
|
||||
},
|
||||
Math.min(this._macAnimationCooldown, 3)
|
||||
); // 最多延迟3ms
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -513,28 +512,16 @@ export class AnimationManager {
|
||||
// Mac设备使用平衡的动画时间控制
|
||||
if (speedFactor > 2) {
|
||||
// 快速操作:快速但平滑
|
||||
duration = Math.min(
|
||||
0.18,
|
||||
Math.max(0.08, (zoomRatio * 0.3) / Math.sqrt(speedFactor))
|
||||
);
|
||||
duration = Math.min(0.18, Math.max(0.08, (zoomRatio * 0.3) / Math.sqrt(speedFactor)));
|
||||
} else if (speedFactor > 1.2) {
|
||||
// 中等速度:标准响应
|
||||
duration = Math.min(
|
||||
0.25,
|
||||
Math.max(0.1, (zoomRatio * 0.4) / Math.sqrt(speedFactor))
|
||||
);
|
||||
duration = Math.min(0.25, Math.max(0.1, (zoomRatio * 0.4) / Math.sqrt(speedFactor)));
|
||||
} else {
|
||||
// 慢速精确操作:确保平滑
|
||||
duration = Math.min(
|
||||
0.3,
|
||||
Math.max(0.12, (zoomRatio * 0.5) / Math.sqrt(speedFactor))
|
||||
);
|
||||
duration = Math.min(0.3, Math.max(0.12, (zoomRatio * 0.5) / Math.sqrt(speedFactor)));
|
||||
}
|
||||
} else {
|
||||
duration = Math.min(
|
||||
0.5,
|
||||
Math.max(0.15, (zoomRatio * 0.8) / Math.sqrt(speedFactor))
|
||||
);
|
||||
duration = Math.min(0.5, Math.max(0.15, (zoomRatio * 0.8) / Math.sqrt(speedFactor)));
|
||||
}
|
||||
|
||||
// 根据滚动速度选择不同的缓动效果
|
||||
|
||||
@@ -125,9 +125,7 @@ export class BaseBrush {
|
||||
_createRGBAColor(color, opacity) {
|
||||
// 如果已经是rgba颜色,先提取RGB部分
|
||||
if (color.startsWith("rgba")) {
|
||||
const rgbaMatch = color.match(
|
||||
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/
|
||||
);
|
||||
const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
|
||||
if (rgbaMatch) {
|
||||
const [, r, g, b] = rgbaMatch;
|
||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
||||
@@ -258,10 +256,7 @@ export class BaseBrush {
|
||||
|
||||
// 过滤掉同名属性(子类优先)
|
||||
const basePropsFiltered = baseProperties.filter(
|
||||
(baseProp) =>
|
||||
!specificProperties.some(
|
||||
(specificProp) => specificProp.id === baseProp.id
|
||||
)
|
||||
(baseProp) => !specificProperties.some((specificProp) => specificProp.id === baseProp.id)
|
||||
);
|
||||
|
||||
return [...basePropsFiltered, ...specificProperties];
|
||||
@@ -334,10 +329,7 @@ export class BaseBrush {
|
||||
* @returns {Array} 选项数组
|
||||
*/
|
||||
getDynamicOptions(property, currentValues) {
|
||||
if (
|
||||
property.dynamicOptions &&
|
||||
typeof property.dynamicOptions === "function"
|
||||
) {
|
||||
if (property.dynamicOptions && typeof property.dynamicOptions === "function") {
|
||||
return property.dynamicOptions(currentValues);
|
||||
}
|
||||
return property.options || [];
|
||||
|
||||
@@ -206,9 +206,7 @@ export class TexturePresetManager {
|
||||
* @returns {Object|null} 材质对象
|
||||
*/
|
||||
getTextureById(textureId) {
|
||||
return (
|
||||
this.getAllTextures().find((texture) => texture.id === textureId) || null
|
||||
);
|
||||
return this.getAllTextures().find((texture) => texture.id === textureId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,9 +215,7 @@ export class TexturePresetManager {
|
||||
* @returns {Array} 材质数组
|
||||
*/
|
||||
getTexturesByCategory(category) {
|
||||
return this.getAllTextures().filter(
|
||||
(texture) => texture.category === category
|
||||
);
|
||||
return this.getAllTextures().filter((texture) => texture.category === category);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,8 +237,7 @@ export class TexturePresetManager {
|
||||
*/
|
||||
addCustomTexture(textureData) {
|
||||
const textureId =
|
||||
textureData.id ||
|
||||
`custom_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
textureData.id || `custom_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const texture = {
|
||||
id: textureId,
|
||||
@@ -286,9 +281,7 @@ export class TexturePresetManager {
|
||||
* @returns {Boolean} 是否删除成功
|
||||
*/
|
||||
removeCustomTexture(textureId) {
|
||||
const index = this.customTextures.findIndex(
|
||||
(texture) => texture.id === textureId
|
||||
);
|
||||
const index = this.customTextures.findIndex((texture) => texture.id === textureId);
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
@@ -430,10 +423,7 @@ export class TexturePresetManager {
|
||||
file: null,
|
||||
}));
|
||||
|
||||
localStorage.setItem(
|
||||
"canvasEditor_customTextures",
|
||||
JSON.stringify(customTexturesData)
|
||||
);
|
||||
localStorage.setItem("canvasEditor_customTextures", JSON.stringify(customTexturesData));
|
||||
} catch (error) {
|
||||
console.error("保存自定义材质失败:", error);
|
||||
}
|
||||
@@ -472,9 +462,7 @@ export class TexturePresetManager {
|
||||
* @returns {String} 预设ID
|
||||
*/
|
||||
createTexturePreset(name, settings) {
|
||||
const presetId = `preset_${Date.now()}_${Math.random()
|
||||
.toString(36)
|
||||
.substr(2, 9)}`;
|
||||
const presetId = `preset_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const preset = {
|
||||
id: presetId,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { MarkerBrush } from "./types/MarkerBrush";
|
||||
import { CustomPenBrush } from "./types/CustomPenBrush";
|
||||
import { RibbonBrush } from "./types/RibbonBrush";
|
||||
import { ShadedBrush } from "./types/ShadedBrush";
|
||||
import { EraserStateManager } from "../EraserStateManager";
|
||||
// import { SketchyBrush } from "./types/SketchyBrush";
|
||||
// import { SpraypaintBrush } from "./types/SpraypaintBrush";
|
||||
|
||||
@@ -379,27 +380,23 @@ export class BrushManager {
|
||||
}
|
||||
// 创建新笔刷实例
|
||||
try {
|
||||
const brushInstance = brushRegistry.createBrushInstance(
|
||||
brushId,
|
||||
this.canvas,
|
||||
{
|
||||
color: brushId === "eraser" ? this.brushStore.state.color : undefined,
|
||||
width: this.brushStore.state.size,
|
||||
opacity: this.brushStore.state.opacity,
|
||||
const brushInstance = brushRegistry.createBrushInstance(brushId, this.canvas, {
|
||||
color: brushId === "eraser" ? this.brushStore.state.color : undefined,
|
||||
width: this.brushStore.state.size,
|
||||
opacity: this.brushStore.state.opacity,
|
||||
|
||||
// 阴影相关配置
|
||||
shadowEnabled: this.brushStore.state.shadowEnabled,
|
||||
shadowColor: this.brushStore.state.shadowColor,
|
||||
shadowWidth: this.brushStore.state.shadowWidth,
|
||||
shadowOffsetX: this.brushStore.state.shadowOffsetX,
|
||||
shadowOffsetY: this.brushStore.state.shadowOffsetY,
|
||||
// 阴影相关配置
|
||||
shadowEnabled: this.brushStore.state.shadowEnabled,
|
||||
shadowColor: this.brushStore.state.shadowColor,
|
||||
shadowWidth: this.brushStore.state.shadowWidth,
|
||||
shadowOffsetX: this.brushStore.state.shadowOffsetX,
|
||||
shadowOffsetY: this.brushStore.state.shadowOffsetY,
|
||||
|
||||
// 材质笔刷特有配置
|
||||
textureEnabled: this.brushStore.state.textureEnabled,
|
||||
texturePath: this.brushStore.state.texturePath,
|
||||
textureScale: this.brushStore.state.textureScale,
|
||||
}
|
||||
);
|
||||
// 材质笔刷特有配置
|
||||
textureEnabled: this.brushStore.state.textureEnabled,
|
||||
texturePath: this.brushStore.state.texturePath,
|
||||
textureScale: this.brushStore.state.textureScale,
|
||||
});
|
||||
|
||||
if (brushInstance) {
|
||||
// 创建笔刷
|
||||
@@ -648,48 +645,7 @@ export class BrushManager {
|
||||
|
||||
// 初始化橡皮擦状态管理器
|
||||
if (this.canvas && this.layerManager) {
|
||||
this.eraserStateManager = new EraserStateManager(
|
||||
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;
|
||||
this.eraserStateManager = new EraserStateManager(this.canvas, this.layerManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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} 橡皮擦笔刷
|
||||
@@ -891,12 +885,7 @@ export class BrushManager {
|
||||
const imageData = ctx.getImageData(pointer.x, pointer.y, 1, 1).data;
|
||||
|
||||
// 将RGB转换为十六进制颜色
|
||||
const color = `#${(
|
||||
(1 << 24) +
|
||||
(imageData[0] << 16) +
|
||||
(imageData[1] << 8) +
|
||||
imageData[2]
|
||||
)
|
||||
const color = `#${((1 << 24) + (imageData[0] << 16) + (imageData[1] << 8) + imageData[2])
|
||||
.toString(16)
|
||||
.slice(1)}`;
|
||||
|
||||
|
||||
@@ -97,9 +97,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
}
|
||||
if (color.indexOf("rgb") === -1) {
|
||||
// convert named colors
|
||||
var tempElem = document.body.appendChild(
|
||||
document.createElement("fictum")
|
||||
); // intentionally use unknown tag to lower chances of css rule override with !important
|
||||
var tempElem = document.body.appendChild(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
|
||||
tempElem.style.color = flag;
|
||||
if (tempElem.style.color !== flag) {
|
||||
@@ -116,7 +114,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
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
|
||||
}
|
||||
return color.match(/[\.\d]+/g).map(function (a) {
|
||||
return color.match(/[.\d]+/g).map(function (a) {
|
||||
return +a;
|
||||
});
|
||||
}
|
||||
@@ -269,13 +267,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
draw: function () {
|
||||
var ctx = this.ctx;
|
||||
ctx.save();
|
||||
this.line(
|
||||
ctx,
|
||||
this._lastPoint,
|
||||
this._point,
|
||||
this.color,
|
||||
this._currentLineWidth
|
||||
);
|
||||
this.line(ctx, this._lastPoint, this._point, this.color, this._currentLineWidth);
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
@@ -319,10 +311,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this.canvas.contextTop.globalAlpha = this.opacity;
|
||||
this._size = this.width / 2 + this._baseWidth;
|
||||
this._drawn = false;
|
||||
@@ -330,10 +320,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this.update(pointer);
|
||||
this.draw(this.canvas.contextTop);
|
||||
},
|
||||
@@ -358,9 +346,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
update: function (p) {
|
||||
this.set(p);
|
||||
this._latestStrokeLength = this._point
|
||||
.subtract(this._latest)
|
||||
.distanceFrom({ x: 0, y: 0 });
|
||||
this._latestStrokeLength = this._point.subtract(this._latest).distanceFrom({ x: 0, y: 0 });
|
||||
},
|
||||
|
||||
draw: function (ctx) {
|
||||
@@ -372,12 +358,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
v.normalize(s);
|
||||
|
||||
dotSize =
|
||||
this._sep *
|
||||
fabric.util.clamp(
|
||||
(this._inkAmount / this._latestStrokeLength) * 3,
|
||||
1,
|
||||
0.5
|
||||
);
|
||||
this._sep * fabric.util.clamp((this._inkAmount / this._latestStrokeLength) * 3, 1, 0.5);
|
||||
dotNum = Math.ceil(this._size * this._sep);
|
||||
|
||||
range = this._size / 2;
|
||||
@@ -428,10 +409,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
// 添加坐标转换处理画布缩放和偏移
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
|
||||
this._points = [pointer];
|
||||
this._count = 0;
|
||||
@@ -440,15 +419,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
color = fabric.util.colorValues(this.color);
|
||||
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
0.1 * this.opacity +
|
||||
")";
|
||||
"rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.1 * this.opacity + ")";
|
||||
ctx.lineWidth = this.width;
|
||||
|
||||
this._points.push(pointer);
|
||||
@@ -456,10 +427,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
// 添加坐标转换处理画布缩放和偏移
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
|
||||
this._points.push(pointer);
|
||||
|
||||
@@ -528,10 +497,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
_render: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
var len,
|
||||
i,
|
||||
point = this.setPointer(pointer),
|
||||
@@ -579,21 +546,11 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
for (i = 0; i < num; i++) {
|
||||
r = fabric.util.getRandom(range, 1);
|
||||
c = fabric.util.getRandom(Math.PI * 2);
|
||||
point = new fabric.Point(
|
||||
pointer.x + r * Math.sin(c),
|
||||
pointer.y + r * Math.cos(c)
|
||||
);
|
||||
point = new fabric.Point(pointer.x + r * Math.sin(c), pointer.y + r * Math.cos(c));
|
||||
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
point.x,
|
||||
point.y,
|
||||
fabric.util.getRandom(maxSize) / 2,
|
||||
0,
|
||||
Math.PI * 2,
|
||||
false
|
||||
);
|
||||
ctx.arc(point.x, point.y, fabric.util.getRandom(maxSize) / 2, 0, Math.PI * 2, false);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.restore();
|
||||
@@ -609,10 +566,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
_resetTip: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
var len,
|
||||
i,
|
||||
point = this.setPointer(pointer);
|
||||
@@ -655,10 +610,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points = [pointer];
|
||||
this._count = 0;
|
||||
|
||||
@@ -667,23 +620,13 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
//ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
0.05 * this.opacity +
|
||||
")";
|
||||
"rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.05 * this.opacity + ")";
|
||||
ctx.lineWidth = this.width;
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points.push(pointer);
|
||||
|
||||
var i,
|
||||
@@ -772,10 +715,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
this._lastPoint.x - lineWidthDiff + num,
|
||||
this._lastPoint.y + lineWidthDiff - num
|
||||
);
|
||||
ctx.lineTo(
|
||||
pointer.x - lineWidthDiff + num,
|
||||
pointer.y + lineWidthDiff - num
|
||||
);
|
||||
ctx.lineTo(pointer.x - lineWidthDiff + num, pointer.y + lineWidthDiff - num);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
@@ -783,10 +723,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = pointer;
|
||||
this.canvas.contextTop.strokeStyle = this.color;
|
||||
this.canvas.contextTop.lineWidth = this._lineWidth;
|
||||
@@ -795,10 +733,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
if (this.canvas._isCurrentlyDrawing) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._render(pointer);
|
||||
}
|
||||
},
|
||||
@@ -852,10 +788,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
this._lastPoint.x + lineWidthDiff - num,
|
||||
this._lastPoint.y + lineWidthDiff - num
|
||||
);
|
||||
ctx.lineTo(
|
||||
pointer.x + lineWidthDiff - num,
|
||||
pointer.y + lineWidthDiff - num
|
||||
);
|
||||
ctx.lineTo(pointer.x + lineWidthDiff - num, pointer.y + lineWidthDiff - num);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
@@ -863,10 +796,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = pointer;
|
||||
this.canvas.contextTop.strokeStyle = this.color;
|
||||
this.canvas.contextTop.lineWidth = this._lineWidth;
|
||||
@@ -875,10 +806,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
if (this.canvas._isCurrentlyDrawing) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._render(pointer);
|
||||
}
|
||||
},
|
||||
@@ -923,10 +852,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points = [];
|
||||
this._points.push(["M", pointer.x, pointer.y]);
|
||||
|
||||
@@ -938,10 +865,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
if (this.canvas._isCurrentlyDrawing) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._render(pointer);
|
||||
}
|
||||
},
|
||||
@@ -1010,10 +935,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
// 添加坐标转换处理画布缩放和偏移
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
|
||||
this._lastPoint = pointer;
|
||||
this.canvas.contextTop.strokeStyle = this.color;
|
||||
@@ -1024,10 +947,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
onMouseMove: function (pointer) {
|
||||
if (this.canvas._isCurrentlyDrawing) {
|
||||
// 添加坐标转换处理画布缩放和偏移
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
|
||||
this._render(pointer);
|
||||
}
|
||||
@@ -1093,10 +1014,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = pointer;
|
||||
this.canvas.contextTop.lineWidth = this._lineWidth;
|
||||
this._size = this.width + this._baseWidth;
|
||||
@@ -1104,10 +1023,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
if (this.canvas._isCurrentlyDrawing) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._render(pointer);
|
||||
}
|
||||
},
|
||||
@@ -1158,12 +1075,10 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(painters[i].dx, painters[i].dy);
|
||||
painters[i].dx -= painters[i].ax =
|
||||
(painters[i].ax +
|
||||
(painters[i].dx - this._lastPoint.x) * painters[i].div) *
|
||||
(painters[i].ax + (painters[i].dx - this._lastPoint.x) * painters[i].div) *
|
||||
painters[i].ease;
|
||||
painters[i].dy -= painters[i].ay =
|
||||
(painters[i].ay +
|
||||
(painters[i].dy - this._lastPoint.y) * painters[i].div) *
|
||||
(painters[i].ay + (painters[i].dy - this._lastPoint.y) * painters[i].div) *
|
||||
painters[i].ease;
|
||||
ctx.lineTo(painters[i].dx, painters[i].dy);
|
||||
ctx.stroke();
|
||||
@@ -1185,26 +1100,16 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
ease: Math.random() * 0.2 + 0.6,
|
||||
});
|
||||
}
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = pointer;
|
||||
|
||||
//ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
0.05 * this.opacity +
|
||||
")";
|
||||
"rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.05 * this.opacity + ")";
|
||||
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].dy = pointer.y;
|
||||
}
|
||||
@@ -1216,10 +1121,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = pointer;
|
||||
},
|
||||
|
||||
@@ -1254,34 +1157,22 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points = [pointer];
|
||||
|
||||
var ctx = this.canvas.contextTop,
|
||||
color = fabric.util.colorValues(this.color);
|
||||
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
this.opacity +
|
||||
")";
|
||||
"rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + this.opacity + ")";
|
||||
ctx.lineWidth = this.width;
|
||||
ctx.lineJoin = ctx.lineCap = "round";
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points.push(pointer);
|
||||
|
||||
var ctx = this.canvas.contextTop,
|
||||
@@ -1346,10 +1237,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
this.canvas.contextTop.globalAlpha = this.opacity;
|
||||
|
||||
// 坐标转换
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
|
||||
this._points = [pointer];
|
||||
this._count = 0;
|
||||
@@ -1361,23 +1250,13 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
ctx.lineWidth = this.width;
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
0.3 * this.opacity +
|
||||
")";
|
||||
"rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.3 * this.opacity + ")";
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
// 坐标转换
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
|
||||
this._points.push(pointer);
|
||||
|
||||
@@ -1396,15 +1275,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
// 增加透明度设置
|
||||
var color = fabric.util.colorValues(this.color);
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
0.2 * this.opacity +
|
||||
")";
|
||||
"rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.2 * this.opacity + ")";
|
||||
|
||||
// 修改循环逻辑,确保在有点时能画出效果
|
||||
if (this._count > 0) {
|
||||
@@ -1416,10 +1287,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
if (d < 4000 && Math.random() > d / 2000) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(
|
||||
points[this._count].x + dx * factor,
|
||||
points[this._count].y + dy * factor
|
||||
);
|
||||
ctx.moveTo(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.stroke();
|
||||
this._drawn = true;
|
||||
@@ -1494,10 +1362,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
this.canvas.contextTop.globalAlpha = this.opacity;
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._point = new fabric.Point(pointer.x, pointer.y);
|
||||
this._lastPoint = this._point;
|
||||
|
||||
@@ -1510,10 +1376,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = this._point;
|
||||
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;
|
||||
y = self._lastPoint.y + Math.cos(angle) - self.size / 2;
|
||||
self.canvas.contextTop.drawImage(
|
||||
self.brush._element,
|
||||
x,
|
||||
y,
|
||||
self.size,
|
||||
self.size
|
||||
);
|
||||
self.canvas.contextTop.drawImage(self.brush._element, x, y, self.size, self.size);
|
||||
|
||||
if (self.canvas._isCurrentlyDrawing) {
|
||||
setTimeout(draw, self._interval);
|
||||
@@ -1594,43 +1452,22 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
var ctx = this.canvas.contextTop,
|
||||
color = fabric.util.colorValues(this.color),
|
||||
bgColor = fabric.util.colorValues(this.bgColor);
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = pointer;
|
||||
this._drawn = false;
|
||||
|
||||
//ctx.globalCompositeOperation = 'source-over';
|
||||
this.canvas.contextTop.globalAlpha = this.opacity;
|
||||
ctx.fillStyle =
|
||||
"rgba(" +
|
||||
bgColor[0] +
|
||||
"," +
|
||||
bgColor[1] +
|
||||
"," +
|
||||
bgColor[2] +
|
||||
"," +
|
||||
bgColor[3] +
|
||||
")";
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
color[3] +
|
||||
")";
|
||||
"rgba(" + bgColor[0] + "," + bgColor[1] + "," + bgColor[2] + "," + bgColor[3] + ")";
|
||||
ctx.strokeStyle = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + color[3] + ")";
|
||||
ctx.lineWidth = this.width;
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
var ctx = this.canvas.contextTop,
|
||||
dx = pointer.x - this._lastPoint.x,
|
||||
dy = pointer.y - this._lastPoint.y,
|
||||
@@ -1683,20 +1520,16 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points = [pointer];
|
||||
this._count = 0;
|
||||
this._colorValues = fabric.util.colorValues(this.color);
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points.push(pointer);
|
||||
|
||||
var ctx = this.canvas.contextTop,
|
||||
|
||||
@@ -350,9 +350,7 @@ export class RainbowBrush extends BaseBrush {
|
||||
];
|
||||
|
||||
// 移除基础属性中的颜色选择器,因为彩虹笔不需要颜色选择
|
||||
const filteredBaseProps = baseProperties.filter(
|
||||
(prop) => prop.id !== "color"
|
||||
);
|
||||
const filteredBaseProps = baseProperties.filter((prop) => prop.id !== "color");
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...filteredBaseProps, ...rainbowProperties];
|
||||
@@ -432,11 +430,7 @@ export class CustomBrushExample extends BaseBrush {
|
||||
this.dashPattern = options.dashPattern || [0, 0]; // 实线
|
||||
this.noiseAmount = options.noiseAmount || 0;
|
||||
this.gradientEnabled = options.gradientEnabled || false;
|
||||
this.gradientColors = options.gradientColors || [
|
||||
"#ff0000",
|
||||
"#ffff00",
|
||||
"#00ff00",
|
||||
];
|
||||
this.gradientColors = options.gradientColors || ["#ff0000", "#ffff00", "#00ff00"];
|
||||
this.angle = options.angle || 0;
|
||||
|
||||
// 材质相关属性
|
||||
@@ -885,8 +879,7 @@ export class CustomBrushExample extends BaseBrush {
|
||||
description: "选择图案类型",
|
||||
category: "特效设置",
|
||||
order: 130,
|
||||
visibleWhen: (values) =>
|
||||
values.effectType === "pattern" || values.patternType === "dashed",
|
||||
visibleWhen: (values) => values.effectType === "pattern" || values.patternType === "dashed",
|
||||
},
|
||||
|
||||
// 虚线特有属性
|
||||
@@ -1184,7 +1177,7 @@ export class CustomBrushExample extends BaseBrush {
|
||||
return true;
|
||||
|
||||
case "dashPattern1":
|
||||
case "dashPattern2":
|
||||
case "dashPattern2": {
|
||||
// 更新虚线模式
|
||||
const idx = propId === "dashPattern1" ? 0 : 1;
|
||||
this.dashPattern[idx] = value;
|
||||
@@ -1192,6 +1185,7 @@ export class CustomBrushExample extends BaseBrush {
|
||||
this.brush.strokeDashArray = this.dashPattern;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case "noiseAmount":
|
||||
this.noiseAmount = value;
|
||||
@@ -1206,13 +1200,14 @@ export class CustomBrushExample extends BaseBrush {
|
||||
|
||||
case "gradientColor1":
|
||||
case "gradientColor2":
|
||||
case "gradientColor3":
|
||||
case "gradientColor3": {
|
||||
const colorIdx = parseInt(propId.slice(-1)) - 1;
|
||||
this.gradientColors[colorIdx] = value;
|
||||
if (this.gradientEnabled && this.brush) {
|
||||
this._configureGradient(this.brush);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case "angle":
|
||||
this.angle = value;
|
||||
|
||||
@@ -24,8 +24,7 @@ export class InkBrush extends BaseBrush {
|
||||
this._baseWidth = options._baseWidth || 15;
|
||||
this._inkAmount = options._inkAmount || 7;
|
||||
this._range = options._range || 10;
|
||||
this.splashEnabled =
|
||||
options.splashEnabled !== undefined ? options.splashEnabled : true;
|
||||
this.splashEnabled = options.splashEnabled !== undefined ? options.splashEnabled : true;
|
||||
this.splashSize = options.splashSize || 5;
|
||||
this.splashDistance = options.splashDistance || 30;
|
||||
}
|
||||
|
||||
@@ -26,9 +26,7 @@ export class LongfurBrush extends BaseBrush {
|
||||
this.furFlowFactor = options.furFlowFactor || 0.5;
|
||||
this.furCurvature = options.furCurvature || 0.3;
|
||||
this.randomizeDirection =
|
||||
options.randomizeDirection !== undefined
|
||||
? options.randomizeDirection
|
||||
: true;
|
||||
options.randomizeDirection !== undefined ? options.randomizeDirection : true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,9 +39,7 @@ export class PencilBrush extends BaseBrush {
|
||||
this.brush = new fabric.PencilBrush(this.canvas);
|
||||
|
||||
// 重写 _finalizeAndAddPath 方法,使其调用 convertToImg 而不是创建 Path 对象
|
||||
const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind(
|
||||
this.brush
|
||||
);
|
||||
const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind(this.brush);
|
||||
const self = this; // 保存外部this引用
|
||||
|
||||
this.brush._finalizeAndAddPath = function () {
|
||||
@@ -54,10 +52,7 @@ export class PencilBrush extends BaseBrush {
|
||||
this._points = this.decimatePoints(this._points, this.decimate);
|
||||
}
|
||||
|
||||
console.log(
|
||||
"PencilBrush: points count =",
|
||||
this._points ? this._points.length : 0
|
||||
);
|
||||
console.log("PencilBrush: points count =", this._points ? this._points.length : 0);
|
||||
|
||||
// 检查是否有有效的路径数据
|
||||
if (!this._points || this._points.length < 2) {
|
||||
@@ -97,9 +92,7 @@ export class PencilBrush extends BaseBrush {
|
||||
// 恢复透明度
|
||||
ctx.globalAlpha = currentAlpha;
|
||||
} else {
|
||||
console.warn(
|
||||
"convertToImg method not found, falling back to original behavior"
|
||||
);
|
||||
console.warn("convertToImg method not found, falling back to original behavior");
|
||||
// 如果没有convertToImg方法,回退到原始行为
|
||||
this.canvas.add(path);
|
||||
this.canvas.fire("path:created", { path: path });
|
||||
@@ -135,11 +128,7 @@ export class PencilBrush extends BaseBrush {
|
||||
const command = pathData[i];
|
||||
if (command[0] === "M") {
|
||||
moveCount++;
|
||||
} else if (
|
||||
command[0] === "L" ||
|
||||
command[0] === "Q" ||
|
||||
command[0] === "C"
|
||||
) {
|
||||
} else if (command[0] === "L" || command[0] === "Q" || command[0] === "C") {
|
||||
hasDrawing = true;
|
||||
break;
|
||||
}
|
||||
@@ -210,9 +199,7 @@ export class PencilBrush extends BaseBrush {
|
||||
_createRGBAColor(color, opacity) {
|
||||
// 如果已经是rgba颜色,先提取RGB部分
|
||||
if (color.startsWith("rgba")) {
|
||||
const rgbaMatch = color.match(
|
||||
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/
|
||||
);
|
||||
const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
|
||||
if (rgbaMatch) {
|
||||
const [, r, g, b] = rgbaMatch;
|
||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
||||
|
||||
@@ -75,10 +75,7 @@ export class RibbonBrush extends BaseBrush {
|
||||
brush.opacity = options.opacity;
|
||||
// 如果启用渐变,更新渐变的第一个颜色
|
||||
if (this.gradient && this.gradientColors.length > 0) {
|
||||
this.gradientColors[0] = this._createRGBAColor(
|
||||
brush.color,
|
||||
options.opacity
|
||||
);
|
||||
this.gradientColors[0] = this._createRGBAColor(brush.color, options.opacity);
|
||||
this.updateGradient();
|
||||
}
|
||||
brush.canvas.freeDrawingBrush.opacity = options.opacity;
|
||||
|
||||
@@ -23,8 +23,7 @@ export class SpraypaintBrush extends BaseBrush {
|
||||
// 喷漆笔刷特有属性
|
||||
this.density = options.density || 20;
|
||||
this.sprayRadius = options.sprayRadius || 10;
|
||||
this.randomOpacity =
|
||||
options.randomOpacity !== undefined ? options.randomOpacity : true;
|
||||
this.randomOpacity = options.randomOpacity !== undefined ? options.randomOpacity : true;
|
||||
this.dotSize = options.dotSize || 1;
|
||||
this.dotShape = options.dotShape || "circle";
|
||||
}
|
||||
|
||||
@@ -71,9 +71,7 @@ export class TextureBrush extends BaseBrush {
|
||||
this.brush = new fabric.PatternBrush(this.canvas);
|
||||
|
||||
// 重写 _finalizeAndAddPath 方法,使其调用 convertToImg 而不是创建 Path 对象
|
||||
const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind(
|
||||
this.brush
|
||||
);
|
||||
const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind(this.brush);
|
||||
const self = this; // 保存外部this引用
|
||||
|
||||
this.brush._finalizeAndAddPath = function () {
|
||||
@@ -86,10 +84,7 @@ export class TextureBrush extends BaseBrush {
|
||||
this._points = this.decimatePoints(this._points, this.decimate);
|
||||
}
|
||||
|
||||
console.log(
|
||||
"TextureBrush: points count =",
|
||||
this._points ? this._points.length : 0
|
||||
);
|
||||
console.log("TextureBrush: points count =", this._points ? this._points.length : 0);
|
||||
|
||||
// 检查是否有有效的路径数据
|
||||
if (!this._points || this._points.length < 2) {
|
||||
@@ -122,9 +117,7 @@ export class TextureBrush extends BaseBrush {
|
||||
this.convertToImg();
|
||||
console.log("TextureBrush: convertToImg called successfully");
|
||||
} else {
|
||||
console.warn(
|
||||
"convertToImg method not found, falling back to original behavior"
|
||||
);
|
||||
console.warn("convertToImg method not found, falling back to original behavior");
|
||||
// 如果没有convertToImg方法,回退到原始行为
|
||||
this.canvas.add(path);
|
||||
this.canvas.fire("path:created", { path: path });
|
||||
@@ -254,10 +247,7 @@ export class TextureBrush extends BaseBrush {
|
||||
this._applyTextureToPatternBrush(img);
|
||||
resolve(img);
|
||||
});
|
||||
} else if (
|
||||
source instanceof Image ||
|
||||
source instanceof HTMLCanvasElement
|
||||
) {
|
||||
} else if (source instanceof Image || source instanceof HTMLCanvasElement) {
|
||||
// 如果已经是Image或Canvas对象,直接使用
|
||||
this._applyTextureToPatternBrush(source);
|
||||
resolve(source);
|
||||
@@ -425,9 +415,7 @@ export class TextureBrush extends BaseBrush {
|
||||
if (textures.length === 0) return Promise.resolve();
|
||||
|
||||
this.currentTextureIndex =
|
||||
this.currentTextureIndex === 0
|
||||
? textures.length - 1
|
||||
: this.currentTextureIndex - 1;
|
||||
this.currentTextureIndex === 0 ? textures.length - 1 : this.currentTextureIndex - 1;
|
||||
const prevTexture = textures[this.currentTextureIndex];
|
||||
|
||||
return this.setTextureById(prevTexture.id);
|
||||
@@ -943,11 +931,7 @@ export class TextureBrush extends BaseBrush {
|
||||
const command = pathData[i];
|
||||
if (command[0] === "M") {
|
||||
moveCount++;
|
||||
} else if (
|
||||
command[0] === "L" ||
|
||||
command[0] === "Q" ||
|
||||
command[0] === "C"
|
||||
) {
|
||||
} else if (command[0] === "L" || command[0] === "Q" || command[0] === "C") {
|
||||
hasDrawing = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -25,9 +25,7 @@ export class WritingBrush extends BaseBrush {
|
||||
this.inkAmount = options.inkAmount || 20;
|
||||
this.brushTaperFactor = options.brushTaperFactor || 0.6;
|
||||
this.enableInkDripping =
|
||||
options.enableInkDripping !== undefined
|
||||
? options.enableInkDripping
|
||||
: true;
|
||||
options.enableInkDripping !== undefined ? options.enableInkDripping : true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,14 +61,10 @@ export class PerformanceManager {
|
||||
: 0;
|
||||
|
||||
const avgUndoTime =
|
||||
this.stats.totalUndos > 0
|
||||
? this.stats.totalUndoTime / this.stats.totalUndos
|
||||
: 0;
|
||||
this.stats.totalUndos > 0 ? this.stats.totalUndoTime / this.stats.totalUndos : 0;
|
||||
|
||||
const avgRedoTime =
|
||||
this.stats.totalRedos > 0
|
||||
? this.stats.totalRedoTime / this.stats.totalRedos
|
||||
: 0;
|
||||
this.stats.totalRedos > 0 ? this.stats.totalRedoTime / this.stats.totalRedos : 0;
|
||||
|
||||
return {
|
||||
overview: {
|
||||
@@ -79,26 +75,18 @@ export class PerformanceManager {
|
||||
avgUndoTime: Number(avgUndoTime.toFixed(2)),
|
||||
avgRedoTime: Number(avgRedoTime.toFixed(2)),
|
||||
},
|
||||
commandBreakdown: Array.from(this.stats.commandStats.entries()).map(
|
||||
([name, stats]) => ({
|
||||
commandName: name,
|
||||
executions: stats.executions,
|
||||
undos: stats.undos,
|
||||
redos: stats.redos,
|
||||
avgExecutionTime:
|
||||
stats.executions > 0
|
||||
? Number((stats.totalExecutionTime / stats.executions).toFixed(2))
|
||||
: 0,
|
||||
avgUndoTime:
|
||||
stats.undos > 0
|
||||
? Number((stats.totalUndoTime / stats.undos).toFixed(2))
|
||||
: 0,
|
||||
avgRedoTime:
|
||||
stats.redos > 0
|
||||
? Number((stats.totalRedoTime / stats.redos).toFixed(2))
|
||||
: 0,
|
||||
})
|
||||
),
|
||||
commandBreakdown: Array.from(this.stats.commandStats.entries()).map(([name, stats]) => ({
|
||||
commandName: name,
|
||||
executions: stats.executions,
|
||||
undos: stats.undos,
|
||||
redos: stats.redos,
|
||||
avgExecutionTime:
|
||||
stats.executions > 0
|
||||
? Number((stats.totalExecutionTime / stats.executions).toFixed(2))
|
||||
: 0,
|
||||
avgUndoTime: 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个操作
|
||||
};
|
||||
}
|
||||
@@ -110,10 +98,8 @@ export class PerformanceManager {
|
||||
const slowCommands = [];
|
||||
|
||||
for (const [name, stats] of this.stats.commandStats.entries()) {
|
||||
const avgExecTime =
|
||||
stats.executions > 0 ? stats.totalExecutionTime / stats.executions : 0;
|
||||
const avgUndoTime =
|
||||
stats.undos > 0 ? stats.totalUndoTime / stats.undos : 0;
|
||||
const avgExecTime = stats.executions > 0 ? stats.totalExecutionTime / stats.executions : 0;
|
||||
const avgUndoTime = stats.undos > 0 ? stats.totalUndoTime / stats.undos : 0;
|
||||
|
||||
if (avgExecTime > threshold || avgUndoTime > threshold) {
|
||||
slowCommands.push({
|
||||
@@ -128,8 +114,7 @@ export class PerformanceManager {
|
||||
|
||||
return slowCommands.sort(
|
||||
(a, b) =>
|
||||
Math.max(b.avgExecutionTime, b.avgUndoTime) -
|
||||
Math.max(a.avgExecutionTime, a.avgUndoTime)
|
||||
Math.max(b.avgExecutionTime, b.avgUndoTime) - Math.max(a.avgExecutionTime, a.avgUndoTime)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,14 +68,9 @@ export class CanvasEventManager {
|
||||
// 让动画管理器自行处理冲突,避免过度干预
|
||||
} else {
|
||||
// 非 Mac 设备的标准处理
|
||||
if (
|
||||
this.animationManager._panAnimation ||
|
||||
this.animationManager._zoomAnimation
|
||||
) {
|
||||
this.animationManager._wasPanning =
|
||||
!!this.animationManager._panAnimation;
|
||||
this.animationManager._wasZooming =
|
||||
!!this.animationManager._zoomAnimation;
|
||||
if (this.animationManager._panAnimation || this.animationManager._zoomAnimation) {
|
||||
this.animationManager._wasPanning = !!this.animationManager._panAnimation;
|
||||
this.animationManager._wasZooming = !!this.animationManager._zoomAnimation;
|
||||
this.animationManager.smoothStopAnimations({ duration: 0.1 });
|
||||
}
|
||||
}
|
||||
@@ -127,8 +122,7 @@ export class CanvasEventManager {
|
||||
|
||||
// Mac 设备优化:触控板滚动通常比鼠标滚轮更敏感
|
||||
if (this.deviceInfo.isMac) {
|
||||
const isMacTrackpadScroll =
|
||||
Math.abs(opt.e.deltaY) < 100 && opt.e.deltaMode === 0;
|
||||
const isMacTrackpadScroll = Math.abs(opt.e.deltaY) < 100 && opt.e.deltaMode === 0;
|
||||
if (isMacTrackpadScroll) {
|
||||
// Mac 触控板滚动更细腻,需要调整滚动因子
|
||||
scrollFactor *= 0.8; // 降低滚动敏感度
|
||||
@@ -183,11 +177,7 @@ export class CanvasEventManager {
|
||||
// 平滑停止任何正在进行的惯性动画
|
||||
this.stopInertiaAnimation(true);
|
||||
|
||||
if (
|
||||
opt.e.altKey ||
|
||||
opt.e.which === 2 ||
|
||||
this.editorMode === OperationType.PAN
|
||||
) {
|
||||
if (opt.e.altKey || opt.e.which === 2 || this.editorMode === OperationType.PAN) {
|
||||
this.canvas.isDragging = true;
|
||||
this.canvas.lastPosX = opt.e.clientX;
|
||||
this.canvas.lastPosY = opt.e.clientY;
|
||||
@@ -247,10 +237,8 @@ export class CanvasEventManager {
|
||||
|
||||
if (opt.e.touches && opt.e.touches.length === 2) {
|
||||
this.canvas.isDragging = true;
|
||||
this.canvas.lastPosX =
|
||||
(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.lastPosX = (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.dragStartTime = Date.now();
|
||||
@@ -290,10 +278,8 @@ export class CanvasEventManager {
|
||||
if (!this.canvas.isDragging) return;
|
||||
|
||||
if (opt.e.touches && opt.e.touches.length === 2) {
|
||||
const currentX =
|
||||
(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 currentX = (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 vpt = this.canvas.viewportTransform;
|
||||
vpt[4] += currentX - this.canvas.lastPosX;
|
||||
@@ -321,8 +307,7 @@ export class CanvasEventManager {
|
||||
|
||||
// 单指拖动更新
|
||||
this.canvas.on("touch:drag:update", (opt) => {
|
||||
if (!this.canvas.isDragging || this.editorMode !== OperationType.PAN)
|
||||
return;
|
||||
if (!this.canvas.isDragging || this.editorMode !== OperationType.PAN) return;
|
||||
|
||||
const currentX = opt.e.touches[0].clientX;
|
||||
const currentY = opt.e.touches[0].clientY;
|
||||
@@ -367,10 +352,7 @@ export class CanvasEventManager {
|
||||
if (this.canvas.isDragging) {
|
||||
// 使用动画管理器处理惯性效果
|
||||
if (this.lastMousePositions.length > 1 && opt && opt.e) {
|
||||
this.animationManager.applyInertiaEffect(
|
||||
this.lastMousePositions,
|
||||
isTouch
|
||||
);
|
||||
this.animationManager.applyInertiaEffect(this.lastMousePositions, isTouch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,22 +387,10 @@ export class CanvasEventManager {
|
||||
});
|
||||
|
||||
// 添加对象开始变换时的状态捕获
|
||||
this.canvas.on(
|
||||
"object:moving",
|
||||
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:moving", 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) => {
|
||||
// 移除调试日志
|
||||
@@ -592,8 +562,7 @@ export class CanvasEventManager {
|
||||
}
|
||||
// 验证是否需要合并
|
||||
const hasExistingObjects =
|
||||
Array.isArray(activeLayer.fabricObjects) &&
|
||||
activeLayer.fabricObjects.length > 0;
|
||||
Array.isArray(activeLayer.fabricObjects) && activeLayer.fabricObjects.length > 0;
|
||||
const hasNewImage = !!fabricImage;
|
||||
|
||||
if (!hasExistingObjects && !hasNewImage) {
|
||||
@@ -611,10 +580,7 @@ export class CanvasEventManager {
|
||||
try {
|
||||
console.log(`开始合并图层 ${activeLayer.name} 中的对象为组...`);
|
||||
|
||||
const command = await this.layerManager.LayerObjectsToGroup(
|
||||
activeLayer,
|
||||
fabricImage
|
||||
);
|
||||
const command = await this.layerManager.LayerObjectsToGroup(activeLayer, fabricImage);
|
||||
|
||||
// 设置命令的撤销状态
|
||||
if (isBoolean(options.undoable)) command.undoable = options.undoable; // 是否撤销
|
||||
@@ -660,9 +626,7 @@ export class CanvasEventManager {
|
||||
|
||||
// 查找对应的图层(现在元素就是图层)
|
||||
const layer = this.layers.value.find(
|
||||
(l) =>
|
||||
l.id === elementId ||
|
||||
(l.fabricObjects && l.fabricObjects?.[0]?.id === layerId)
|
||||
(l) => l.fabricObjects && l.fabricObjects?.[0]?.id === layerId
|
||||
);
|
||||
|
||||
if (layer) {
|
||||
@@ -745,8 +709,7 @@ export class CanvasEventManager {
|
||||
_detectDeviceType() {
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
const platform = navigator.platform.toLowerCase();
|
||||
const hasTouchSupport =
|
||||
"ontouchstart" in window || navigator.maxTouchPoints > 0;
|
||||
const hasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
// 检测操作系统
|
||||
const isMac = /mac|darwin/.test(platform) || /macintosh/.test(userAgent);
|
||||
|
||||
@@ -92,9 +92,7 @@ export class KeyboardManager {
|
||||
*/
|
||||
detectTouchDevice() {
|
||||
return (
|
||||
"ontouchstart" in window ||
|
||||
navigator.maxTouchPoints > 0 ||
|
||||
navigator.msMaxTouchPoints > 0
|
||||
"ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0
|
||||
);
|
||||
}
|
||||
|
||||
@@ -214,9 +212,7 @@ export class KeyboardManager {
|
||||
this.container.addEventListener("touchcancel", this._handleTouchEnd);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`键盘管理器已初始化,平台: ${this.platform}, 触摸设备: ${this.isTouchDevice}`
|
||||
);
|
||||
console.log(`键盘管理器已初始化,平台: ${this.platform}, 触摸设备: ${this.isTouchDevice}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,10 +258,7 @@ export class KeyboardManager {
|
||||
|
||||
// 调用自定义处理程序
|
||||
const key = event.key.toLowerCase();
|
||||
if (
|
||||
this.customHandlers[key] &&
|
||||
typeof this.customHandlers[key].onKeyUp === "function"
|
||||
) {
|
||||
if (this.customHandlers[key] && typeof this.customHandlers[key].onKeyUp === "function") {
|
||||
this.customHandlers[key].onKeyUp(event);
|
||||
}
|
||||
}
|
||||
@@ -281,15 +274,11 @@ export class KeyboardManager {
|
||||
if (touches.length === 2) {
|
||||
// 双指触摸 - 可用于缩放或调整画笔大小
|
||||
this.touchState.isTwoFingerTouch = true;
|
||||
this.touchState.pinchStartDistance = this.getDistanceBetweenTouches(
|
||||
touches[0],
|
||||
touches[1]
|
||||
);
|
||||
this.touchState.pinchStartDistance = this.getDistanceBetweenTouches(touches[0], touches[1]);
|
||||
|
||||
// 如果有画笔管理器,记录起始画笔大小
|
||||
if (this.toolManager && this.toolManager.brushManager) {
|
||||
this.touchState.pinchStartBrushSize =
|
||||
this.toolManager.brushManager.brushSize.value;
|
||||
this.touchState.pinchStartBrushSize = this.toolManager.brushManager.brushSize.value;
|
||||
}
|
||||
} else if (touches.length === 3) {
|
||||
// 三指触摸 - 可用于撤销/重做
|
||||
@@ -311,10 +300,7 @@ export class KeyboardManager {
|
||||
|
||||
// 双指缩放处理 - 调整画笔大小
|
||||
if (touches.length === 2 && this.touchState.isTwoFingerTouch) {
|
||||
const currentDistance = this.getDistanceBetweenTouches(
|
||||
touches[0],
|
||||
touches[1]
|
||||
);
|
||||
const currentDistance = this.getDistanceBetweenTouches(touches[0], touches[1]);
|
||||
const scale = currentDistance / this.touchState.pinchStartDistance;
|
||||
|
||||
// 调整画笔大小
|
||||
@@ -587,10 +573,7 @@ export class KeyboardManager {
|
||||
let shortcutKey = "";
|
||||
|
||||
// 统一处理Mac和PC的修饰键
|
||||
if (
|
||||
(this.platform === "mac" && event.metaKey) ||
|
||||
(this.platform !== "mac" && event.ctrlKey)
|
||||
) {
|
||||
if ((this.platform === "mac" && event.metaKey) || (this.platform !== "mac" && event.ctrlKey)) {
|
||||
shortcutKey += `${this.modifierKeys.cmdOrCtrl}+`;
|
||||
} else if (event.ctrlKey) {
|
||||
shortcutKey += "ctrl+";
|
||||
|
||||
@@ -216,9 +216,7 @@ export class EnhancedLiquifyManager {
|
||||
// 计算图像大小
|
||||
const pixelCount = imageData.width * imageData.height;
|
||||
|
||||
console.log(
|
||||
`液化选择渲染器: 图像大小=${pixelCount}像素, WebGL可用=${this.isWebGLAvailable}`
|
||||
);
|
||||
console.log(`液化选择渲染器: 图像大小=${pixelCount}像素, WebGL可用=${this.isWebGLAvailable}`);
|
||||
|
||||
// 默认使用CPU渲染器
|
||||
this.activeRenderer = this.cpuRenderer;
|
||||
@@ -249,11 +247,7 @@ export class EnhancedLiquifyManager {
|
||||
this.activeRenderer = this.webglRenderer;
|
||||
this.renderMode = "webgl";
|
||||
} else {
|
||||
console.log(
|
||||
`液化功能: 使用CPU渲染模式${
|
||||
!this.isWebGLAvailable ? " (WebGL不可用)" : ""
|
||||
}`
|
||||
);
|
||||
console.log(`液化功能: 使用CPU渲染模式${!this.isWebGLAvailable ? " (WebGL不可用)" : ""}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,16 +312,11 @@ export class EnhancedLiquifyManager {
|
||||
this.params[param] = value;
|
||||
|
||||
// 同步更新当前渲染器 - 关键修复:确保参数正确传递
|
||||
if (
|
||||
this.activeRenderer &&
|
||||
typeof this.activeRenderer.setParam === "function"
|
||||
) {
|
||||
if (this.activeRenderer && typeof this.activeRenderer.setParam === "function") {
|
||||
console.log(`EnhancedLiquifyManager 设置参数: ${param}=${value}`);
|
||||
this.activeRenderer.setParam(param, value);
|
||||
} else {
|
||||
console.warn(
|
||||
`EnhancedLiquifyManager: 无法设置参数 ${param},渲染器未就绪`
|
||||
);
|
||||
console.warn(`EnhancedLiquifyManager: 无法设置参数 ${param},渲染器未就绪`);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -382,25 +371,17 @@ export class EnhancedLiquifyManager {
|
||||
* @param {Number} y 初始Y坐标
|
||||
*/
|
||||
startLiquifyOperation(x, y) {
|
||||
if (
|
||||
this.activeRenderer &&
|
||||
typeof this.activeRenderer.startDeformation === "function"
|
||||
) {
|
||||
if (this.activeRenderer && typeof this.activeRenderer.startDeformation === "function") {
|
||||
this.activeRenderer.startDeformation(x, y);
|
||||
}
|
||||
console.log(
|
||||
`开始液化操作,渲染模式=${this.renderMode}, 初始点: (${x}, ${y})`
|
||||
);
|
||||
console.log(`开始液化操作,渲染模式=${this.renderMode}, 初始点: (${x}, ${y})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束液化操作
|
||||
*/
|
||||
endLiquifyOperation() {
|
||||
if (
|
||||
this.activeRenderer &&
|
||||
typeof this.activeRenderer.endDeformation === "function"
|
||||
) {
|
||||
if (this.activeRenderer && typeof this.activeRenderer.endDeformation === "function") {
|
||||
this.activeRenderer.endDeformation();
|
||||
}
|
||||
console.log(`结束液化操作,渲染模式=${this.renderMode}`);
|
||||
@@ -443,9 +424,7 @@ export class EnhancedLiquifyManager {
|
||||
|
||||
// 坐标边界检查
|
||||
if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight) {
|
||||
console.warn(
|
||||
`液化坐标超出图像范围: (${x}, ${y}), 图像尺寸: ${imageWidth}x${imageHeight}`
|
||||
);
|
||||
console.warn(`液化坐标超出图像范围: (${x}, ${y}), 图像尺寸: ${imageWidth}x${imageHeight}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -510,9 +489,7 @@ export class EnhancedLiquifyManager {
|
||||
console.log(
|
||||
`液化性能数据: 模式=${this.renderMode}, 平均耗时=${avgTime.toFixed(
|
||||
2
|
||||
)}ms, 图像尺寸=${this.originalImageData?.width}x${
|
||||
this.originalImageData?.height
|
||||
}`
|
||||
)}ms, 图像尺寸=${this.originalImageData?.width}x${this.originalImageData?.height}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -604,8 +581,7 @@ export class EnhancedLiquifyManager {
|
||||
const singleObject = objectsToCheck.length === 1;
|
||||
const isImage =
|
||||
singleObject &&
|
||||
(objectsToCheck[0].type === "image" ||
|
||||
objectsToCheck[0].type === "rasterized-layer");
|
||||
(objectsToCheck[0].type === "image" || objectsToCheck[0].type === "rasterized-layer");
|
||||
|
||||
// 检查是否为组
|
||||
const isGroup =
|
||||
@@ -655,19 +631,14 @@ export class EnhancedLiquifyManager {
|
||||
tempCanvas.height = fabricObject.height;
|
||||
const tempCtx = tempCanvas.getContext("2d");
|
||||
|
||||
console.log(
|
||||
`创建临时Canvas,尺寸: ${tempCanvas.width}x${tempCanvas.height}`
|
||||
);
|
||||
console.log(`创建临时Canvas,尺寸: ${tempCanvas.width}x${tempCanvas.height}`);
|
||||
|
||||
// 处理不同的图像源
|
||||
if (fabricObject._element) {
|
||||
console.log("使用 _element 绘制图像");
|
||||
|
||||
// 检查_element是否有效
|
||||
if (
|
||||
!fabricObject._element.complete &&
|
||||
fabricObject._element.tagName === "IMG"
|
||||
) {
|
||||
if (!fabricObject._element.complete && fabricObject._element.tagName === "IMG") {
|
||||
console.log("图像未加载完成,等待加载...");
|
||||
fabricObject._element.onload = () => {
|
||||
try {
|
||||
@@ -678,12 +649,7 @@ export class EnhancedLiquifyManager {
|
||||
fabricObject.width,
|
||||
fabricObject.height
|
||||
);
|
||||
const imageData = tempCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
console.log("✅ 图像加载完成后获取数据成功");
|
||||
resolve(imageData);
|
||||
} catch (error) {
|
||||
@@ -698,17 +664,8 @@ export class EnhancedLiquifyManager {
|
||||
}
|
||||
|
||||
// 直接绘制已加载的图像
|
||||
tempCtx.drawImage(
|
||||
fabricObject._element,
|
||||
0,
|
||||
0,
|
||||
fabricObject.width,
|
||||
fabricObject.height
|
||||
);
|
||||
} else if (
|
||||
fabricObject.getSrc &&
|
||||
typeof fabricObject.getSrc === "function"
|
||||
) {
|
||||
tempCtx.drawImage(fabricObject._element, 0, 0, fabricObject.width, fabricObject.height);
|
||||
} else if (fabricObject.getSrc && typeof fabricObject.getSrc === "function") {
|
||||
console.log("使用 getSrc() 方法获取图像源");
|
||||
|
||||
// 通过URL创建图像
|
||||
@@ -717,24 +674,11 @@ export class EnhancedLiquifyManager {
|
||||
|
||||
img.onload = () => {
|
||||
try {
|
||||
console.log(
|
||||
`图像加载成功,原始尺寸: ${img.naturalWidth}x${img.naturalHeight}`
|
||||
);
|
||||
console.log(`图像加载成功,原始尺寸: ${img.naturalWidth}x${img.naturalHeight}`);
|
||||
|
||||
tempCtx.drawImage(
|
||||
img,
|
||||
0,
|
||||
0,
|
||||
fabricObject.width,
|
||||
fabricObject.height
|
||||
);
|
||||
tempCtx.drawImage(img, 0, 0, fabricObject.width, fabricObject.height);
|
||||
|
||||
const imageData = tempCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
|
||||
console.log("✅ 通过URL获取图像数据成功");
|
||||
resolve(imageData);
|
||||
@@ -762,20 +706,9 @@ export class EnhancedLiquifyManager {
|
||||
|
||||
img.onload = () => {
|
||||
try {
|
||||
tempCtx.drawImage(
|
||||
img,
|
||||
0,
|
||||
0,
|
||||
fabricObject.width,
|
||||
fabricObject.height
|
||||
);
|
||||
tempCtx.drawImage(img, 0, 0, fabricObject.width, fabricObject.height);
|
||||
|
||||
const imageData = tempCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
|
||||
console.log("✅ 通过src属性获取图像数据成功");
|
||||
resolve(imageData);
|
||||
@@ -795,20 +728,13 @@ export class EnhancedLiquifyManager {
|
||||
return;
|
||||
} else {
|
||||
console.error("无法找到有效的图像源");
|
||||
reject(
|
||||
new Error("图像对象缺少有效的图像源(_element, getSrc, 或 src)")
|
||||
);
|
||||
reject(new Error("图像对象缺少有效的图像源(_element, getSrc, 或 src)"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果走到这里,说明使用了_element直接绘制
|
||||
try {
|
||||
const imageData = tempCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
|
||||
console.log(
|
||||
`✅ 获取图像数据成功: 对象尺寸=${fabricObject.width}x${fabricObject.height}, ` +
|
||||
|
||||
@@ -178,10 +178,7 @@ export class HybridLiquifyManager {
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 5.0);
|
||||
const baseRotationSpeed = 0.015;
|
||||
const rotationAngle =
|
||||
(clockwise ? 1 : -1) *
|
||||
baseRotationSpeed *
|
||||
strength *
|
||||
(1.0 + timeFactor * 0.3);
|
||||
(clockwise ? 1 : -1) * baseRotationSpeed * strength * (1.0 + timeFactor * 0.3);
|
||||
|
||||
this.accumulatedRotation += rotationAngle;
|
||||
|
||||
@@ -209,13 +206,7 @@ export class HybridLiquifyManager {
|
||||
const sourceY = centerY + Math.sin(newAngle) * distance;
|
||||
|
||||
// 双线性插值采样
|
||||
const color = this._bilinearSample(
|
||||
srcData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(srcData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
@@ -232,16 +223,7 @@ export class HybridLiquifyManager {
|
||||
/**
|
||||
* 像素级水晶效果
|
||||
*/
|
||||
_applyPixelCrystal(
|
||||
srcData,
|
||||
dstData,
|
||||
width,
|
||||
height,
|
||||
centerX,
|
||||
centerY,
|
||||
radius,
|
||||
strength
|
||||
) {
|
||||
_applyPixelCrystal(srcData, dstData, width, height, centerX, centerY, radius, strength) {
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 3.0);
|
||||
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 wave2 = Math.cos(angle * 12 + this.pressDuration * 0.003) * 0.4;
|
||||
const waveAngle =
|
||||
angle + (wave1 + wave2) * distortionStrength * falloff;
|
||||
const waveAngle = angle + (wave1 + wave2) * distortionStrength * falloff;
|
||||
|
||||
const radialMod =
|
||||
1 +
|
||||
Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) *
|
||||
0.3;
|
||||
1 + Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) * 0.3;
|
||||
const modDistance = distance * radialMod;
|
||||
|
||||
const sourceX = centerX + Math.cos(waveAngle) * modDistance;
|
||||
const sourceY = centerY + Math.sin(waveAngle) * modDistance;
|
||||
|
||||
const color = this._bilinearSample(
|
||||
srcData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(srcData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
@@ -311,16 +284,7 @@ export class HybridLiquifyManager {
|
||||
/**
|
||||
* 像素级边缘效果
|
||||
*/
|
||||
_applyPixelEdge(
|
||||
srcData,
|
||||
dstData,
|
||||
width,
|
||||
height,
|
||||
centerX,
|
||||
centerY,
|
||||
radius,
|
||||
strength
|
||||
) {
|
||||
_applyPixelEdge(srcData, dstData, width, height, centerX, centerY, radius, strength) {
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 2.5);
|
||||
const edgeStrength = strength * (1.0 + timeFactor * 0.4);
|
||||
|
||||
@@ -354,13 +318,7 @@ export class HybridLiquifyManager {
|
||||
const sourceX = x + offsetX;
|
||||
const sourceY = y + offsetY;
|
||||
|
||||
const color = this._bilinearSample(
|
||||
srcData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(srcData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
|
||||
@@ -79,12 +79,7 @@ export class LiquifyCPUManager {
|
||||
this.canvas.width = imageSource.width;
|
||||
this.canvas.height = imageSource.height;
|
||||
this.ctx.drawImage(imageSource, 0, 0);
|
||||
this.originalImageData = this.ctx.getImageData(
|
||||
0,
|
||||
0,
|
||||
imageSource.width,
|
||||
imageSource.height
|
||||
);
|
||||
this.originalImageData = this.ctx.getImageData(0, 0, imageSource.width, imageSource.height);
|
||||
} else {
|
||||
throw new Error("不支持的图像类型");
|
||||
}
|
||||
@@ -95,10 +90,7 @@ export class LiquifyCPUManager {
|
||||
this.originalImageData.height
|
||||
);
|
||||
|
||||
this._initMesh(
|
||||
this.originalImageData.width,
|
||||
this.originalImageData.height
|
||||
);
|
||||
this._initMesh(this.originalImageData.width, this.originalImageData.height);
|
||||
this.initialized = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -268,13 +260,7 @@ export class LiquifyCPUManager {
|
||||
* @param {number} strength 强度
|
||||
* @param {boolean} isClockwise 是否顺时针旋转
|
||||
*/
|
||||
_applyEnhancedRotationDeformation(
|
||||
centerX,
|
||||
centerY,
|
||||
radius,
|
||||
strength,
|
||||
isClockwise
|
||||
) {
|
||||
_applyEnhancedRotationDeformation(centerX, centerY, radius, strength, isClockwise) {
|
||||
if (!this.currentImageData) return;
|
||||
|
||||
const data = this.currentImageData.data;
|
||||
@@ -287,11 +273,7 @@ export class LiquifyCPUManager {
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 5.0);
|
||||
const baseRotationSpeed = 0.02; // 使用与测试文件相同的速度
|
||||
const rotationAngle =
|
||||
(isClockwise ? 1 : -1) *
|
||||
baseRotationSpeed *
|
||||
pressure *
|
||||
power *
|
||||
(1.0 + timeFactor * 0.5);
|
||||
(isClockwise ? 1 : -1) * baseRotationSpeed * pressure * power * (1.0 + timeFactor * 0.5);
|
||||
|
||||
// 累积旋转角度 - 关键:这确保了持续旋转效果
|
||||
this.accumulatedRotation += rotationAngle;
|
||||
@@ -322,13 +304,7 @@ export class LiquifyCPUManager {
|
||||
const sourceY = centerY + Math.sin(newAngle) * distance;
|
||||
|
||||
// 双线性插值采样 - 确保像素连续性
|
||||
const color = this._bilinearSample(
|
||||
tempData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
@@ -389,13 +365,7 @@ export class LiquifyCPUManager {
|
||||
const sourceY = centerY + dy * scale;
|
||||
|
||||
// 双线性插值采样
|
||||
const color = this._bilinearSample(
|
||||
tempData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
@@ -461,13 +431,7 @@ export class LiquifyCPUManager {
|
||||
const sourceX = x - pushX;
|
||||
const sourceY = y - pushY;
|
||||
|
||||
const color = this._bilinearSample(
|
||||
tempData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
@@ -504,13 +468,7 @@ export class LiquifyCPUManager {
|
||||
const sourceX = x - offsetX;
|
||||
const sourceY = y - offsetY;
|
||||
|
||||
const color = this._bilinearSample(
|
||||
tempData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
@@ -561,7 +519,7 @@ export class LiquifyCPUManager {
|
||||
this._applyEnhancedPushDeformation(x, y, radius, strength);
|
||||
break;
|
||||
|
||||
default:
|
||||
default: {
|
||||
// 对于其他模式,使用原有的网格算法
|
||||
if (!this.mesh) return;
|
||||
|
||||
@@ -569,20 +527,14 @@ export class LiquifyCPUManager {
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 4.0);
|
||||
const finalStrength = baseStrength * (1.0 + timeFactor * 0.5);
|
||||
|
||||
this._applyDeformation(
|
||||
x,
|
||||
y,
|
||||
radius,
|
||||
finalStrength,
|
||||
mode,
|
||||
this.params.distortion
|
||||
);
|
||||
this._applyDeformation(x, y, radius, finalStrength, mode, this.params.distortion);
|
||||
|
||||
if (this.config.smoothingIterations > 0) {
|
||||
this._lightSmoothing();
|
||||
}
|
||||
|
||||
return this._applyMeshToImage();
|
||||
}
|
||||
}
|
||||
|
||||
// 对于像素算法,直接返回当前图像数据
|
||||
@@ -592,96 +544,96 @@ export class LiquifyCPUManager {
|
||||
/**
|
||||
* 应用液化变形 - 主要入口,集成增强算法
|
||||
*/
|
||||
applyDeformation(x, y) {
|
||||
if (!this.initialized || !this.originalImageData) {
|
||||
console.warn("液化管理器未初始化或缺少必要数据");
|
||||
return this.currentImageData;
|
||||
}
|
||||
// applyDeformation(x, y) {
|
||||
// if (!this.initialized || !this.originalImageData) {
|
||||
// console.warn("液化管理器未初始化或缺少必要数据");
|
||||
// return this.currentImageData;
|
||||
// }
|
||||
|
||||
// 更新鼠标位置
|
||||
this.currentMouseX = x;
|
||||
this.currentMouseY = y;
|
||||
// // 更新鼠标位置
|
||||
// this.currentMouseX = x;
|
||||
// this.currentMouseY = y;
|
||||
|
||||
// 计算拖拽参数
|
||||
const deltaX = this.currentMouseX - this.initialMouseX;
|
||||
const deltaY = this.currentMouseY - this.initialMouseY;
|
||||
this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
this.dragAngle = Math.atan2(deltaY, deltaX);
|
||||
// // 计算拖拽参数
|
||||
// const deltaX = this.currentMouseX - this.initialMouseX;
|
||||
// const deltaY = this.currentMouseY - this.initialMouseY;
|
||||
// this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
// this.dragAngle = Math.atan2(deltaY, deltaX);
|
||||
|
||||
// 获取当前参数
|
||||
const { size, pressure, power } = this.params;
|
||||
const mode = this.currentMode;
|
||||
const radius = size;
|
||||
const strength = pressure * power;
|
||||
// // 获取当前参数
|
||||
// const { size, pressure, power } = this.params;
|
||||
// const mode = this.currentMode;
|
||||
// const radius = size;
|
||||
// const strength = pressure * power;
|
||||
|
||||
// 根据模式选择算法
|
||||
const pixelModes = [
|
||||
this.modes.CLOCKWISE,
|
||||
this.modes.COUNTERCLOCKWISE,
|
||||
this.modes.PINCH,
|
||||
this.modes.EXPAND,
|
||||
this.modes.PUSH,
|
||||
];
|
||||
// // 根据模式选择算法
|
||||
// const pixelModes = [
|
||||
// this.modes.CLOCKWISE,
|
||||
// this.modes.COUNTERCLOCKWISE,
|
||||
// this.modes.PINCH,
|
||||
// this.modes.EXPAND,
|
||||
// this.modes.PUSH,
|
||||
// ];
|
||||
|
||||
if (pixelModes.includes(mode)) {
|
||||
// 使用增强的像素算法
|
||||
switch (mode) {
|
||||
case this.modes.CLOCKWISE:
|
||||
this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
|
||||
break;
|
||||
case this.modes.COUNTERCLOCKWISE:
|
||||
this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
|
||||
break;
|
||||
case this.modes.PINCH:
|
||||
this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
|
||||
break;
|
||||
case this.modes.EXPAND:
|
||||
this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
|
||||
break;
|
||||
case this.modes.PUSH:
|
||||
this._applyEnhancedPushDeformation(x, y, radius, strength);
|
||||
break;
|
||||
}
|
||||
// if (pixelModes.includes(mode)) {
|
||||
// // 使用增强的像素算法
|
||||
// switch (mode) {
|
||||
// case this.modes.CLOCKWISE:
|
||||
// this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
|
||||
// break;
|
||||
// case this.modes.COUNTERCLOCKWISE:
|
||||
// this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
|
||||
// break;
|
||||
// case this.modes.PINCH:
|
||||
// this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
|
||||
// break;
|
||||
// case this.modes.EXPAND:
|
||||
// this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
|
||||
// break;
|
||||
// case this.modes.PUSH:
|
||||
// this._applyEnhancedPushDeformation(x, y, radius, strength);
|
||||
// break;
|
||||
// }
|
||||
|
||||
// 更新最后应用时间
|
||||
this.lastApplyTime = Date.now();
|
||||
this.isFirstApply = false;
|
||||
// // 更新最后应用时间
|
||||
// this.lastApplyTime = Date.now();
|
||||
// this.isFirstApply = false;
|
||||
|
||||
return this.currentImageData;
|
||||
} else {
|
||||
// 使用原有的网格算法处理其他模式
|
||||
if (!this.mesh) {
|
||||
console.warn("网格未初始化");
|
||||
return this.currentImageData;
|
||||
}
|
||||
// return this.currentImageData;
|
||||
// } else {
|
||||
// // 使用原有的网格算法处理其他模式
|
||||
// if (!this.mesh) {
|
||||
// console.warn("网格未初始化");
|
||||
// return this.currentImageData;
|
||||
// }
|
||||
|
||||
const finalStrength = (strength * this.config.maxStrength) / 100;
|
||||
// const finalStrength = (strength * this.config.maxStrength) / 100;
|
||||
|
||||
// 应用变形
|
||||
this._applyDeformation(
|
||||
x,
|
||||
y,
|
||||
radius,
|
||||
finalStrength,
|
||||
mode,
|
||||
this.params.distortion
|
||||
);
|
||||
// // 应用变形
|
||||
// this._applyDeformation(
|
||||
// x,
|
||||
// y,
|
||||
// radius,
|
||||
// finalStrength,
|
||||
// mode,
|
||||
// this.params.distortion,
|
||||
// );
|
||||
|
||||
// 平滑处理
|
||||
if (this.config.smoothingIterations > 0) {
|
||||
this._smoothMesh();
|
||||
}
|
||||
// // 平滑处理
|
||||
// if (this.config.smoothingIterations > 0) {
|
||||
// this._smoothMesh();
|
||||
// }
|
||||
|
||||
// 更新图像数据
|
||||
const result = this._applyMeshToImage();
|
||||
// // 更新图像数据
|
||||
// const result = this._applyMeshToImage();
|
||||
|
||||
// 更新最后应用时间
|
||||
this.lastApplyTime = Date.now();
|
||||
this.isFirstApply = false;
|
||||
// // 更新最后应用时间
|
||||
// this.lastApplyTime = Date.now();
|
||||
// this.isFirstApply = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// return result;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 双线性插值采样 - 用于像素级算法
|
||||
@@ -773,22 +725,14 @@ export class LiquifyCPUManager {
|
||||
|
||||
const baseDistortion = Math.max(distortion, 0.3);
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 2.0);
|
||||
const timeEnhancedDistortion =
|
||||
baseDistortion * (1.0 + timeFactor * 0.3);
|
||||
const timeEnhancedDistortion = baseDistortion * (1.0 + timeFactor * 0.3);
|
||||
|
||||
const wave1 =
|
||||
Math.sin(crystalAngle * 8 + this.pressDuration * 0.005) * 0.6;
|
||||
const wave2 =
|
||||
Math.cos(crystalAngle * 12 + this.pressDuration * 0.003) * 0.4;
|
||||
const waveAngle =
|
||||
crystalAngle + (wave1 + wave2) * timeEnhancedDistortion;
|
||||
const wave1 = Math.sin(crystalAngle * 8 + this.pressDuration * 0.005) * 0.6;
|
||||
const wave2 = Math.cos(crystalAngle * 12 + this.pressDuration * 0.003) * 0.4;
|
||||
const waveAngle = crystalAngle + (wave1 + wave2) * timeEnhancedDistortion;
|
||||
|
||||
const radialMod =
|
||||
1 +
|
||||
Math.sin(
|
||||
crystalRadius * Math.PI * 2 + this.pressDuration * 0.002
|
||||
) *
|
||||
0.3;
|
||||
1 + Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) * 0.3;
|
||||
const modDistance = distance * radialMod;
|
||||
|
||||
const crystalX = x + Math.cos(waveAngle) * modDistance;
|
||||
@@ -809,8 +753,7 @@ export class LiquifyCPUManager {
|
||||
|
||||
const baseEdgeDistortion = Math.max(distortion, 0.5);
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 2.5);
|
||||
const timeEnhancedDistortion =
|
||||
baseEdgeDistortion * (1.0 + timeFactor * 0.4);
|
||||
const timeEnhancedDistortion = baseEdgeDistortion * (1.0 + timeFactor * 0.4);
|
||||
|
||||
const edgeWave =
|
||||
Math.sin(edgeRadius * Math.PI * 4 + this.pressDuration * 0.004) *
|
||||
@@ -887,11 +830,7 @@ export class LiquifyCPUManager {
|
||||
const points = this.mesh.deformedPoints;
|
||||
const tempPoints = points.map((p) => ({ x: p.x, y: p.y }));
|
||||
|
||||
for (
|
||||
let iteration = 0;
|
||||
iteration < this.config.smoothingIterations;
|
||||
iteration++
|
||||
) {
|
||||
for (let iteration = 0; iteration < this.config.smoothingIterations; iteration++) {
|
||||
for (let y = 1; y < rows; y++) {
|
||||
for (let x = 1; x < cols; x++) {
|
||||
const idx = y * (cols + 1) + x;
|
||||
@@ -996,19 +935,8 @@ export class LiquifyCPUManager {
|
||||
for (let x = 0; x < width; x += step) {
|
||||
const srcPos = this._mapPointBack(x, y);
|
||||
|
||||
if (
|
||||
srcPos.x >= 0 &&
|
||||
srcPos.x < width &&
|
||||
srcPos.y >= 0 &&
|
||||
srcPos.y < height
|
||||
) {
|
||||
const color = this._bilinearInterpolate(
|
||||
srcData,
|
||||
width,
|
||||
height,
|
||||
srcPos.x,
|
||||
srcPos.y
|
||||
);
|
||||
if (srcPos.x >= 0 && 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++) {
|
||||
@@ -1070,22 +998,14 @@ export class LiquifyCPUManager {
|
||||
// 根据推拉模式和拖拽距离动态调整强度
|
||||
let strength;
|
||||
if (mode === this.modes.PUSH) {
|
||||
const baseStrength =
|
||||
(pressure * power * this.config.maxStrength) / 100;
|
||||
const baseStrength = (pressure * power * this.config.maxStrength) / 100;
|
||||
const distanceFactor = Math.min(this.dragDistance / radius, 2.0);
|
||||
strength = baseStrength * distanceFactor * 0.3; // 批量处理时降低强度
|
||||
} else {
|
||||
strength = (pressure * power * this.config.maxStrength) / 100;
|
||||
}
|
||||
|
||||
this._applyDeformation(
|
||||
pos.x,
|
||||
pos.y,
|
||||
radius,
|
||||
strength,
|
||||
mode,
|
||||
distortion
|
||||
);
|
||||
this._applyDeformation(pos.x, pos.y, radius, strength, mode, distortion);
|
||||
});
|
||||
|
||||
// 结束拖拽操作
|
||||
@@ -1331,14 +1251,7 @@ export class LiquifyCPUManager {
|
||||
const finalStrength = (strength * this.config.maxStrength) / 100;
|
||||
|
||||
// 应用变形
|
||||
this._applyDeformation(
|
||||
x,
|
||||
y,
|
||||
radius,
|
||||
finalStrength,
|
||||
mode,
|
||||
this.params.distortion
|
||||
);
|
||||
this._applyDeformation(x, y, radius, finalStrength, mode, this.params.distortion);
|
||||
|
||||
// 平滑处理
|
||||
if (this.config.smoothingIterations > 0) {
|
||||
|
||||
@@ -130,32 +130,18 @@ export class LiquifyManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`LiquifyManager.applyLiquify: 模式=${mode}, 坐标=(${x}, ${y}), 参数=`,
|
||||
params
|
||||
);
|
||||
console.log(`LiquifyManager.applyLiquify: 模式=${mode}, 坐标=(${x}, ${y}), 参数=`, params);
|
||||
|
||||
try {
|
||||
// 直接调用EnhancedLiquifyManager的applyLiquify方法
|
||||
// 避免重复设置参数,让EnhancedLiquifyManager处理参数设置
|
||||
const resultData = await this.enhancedManager.applyLiquify(
|
||||
targetObject,
|
||||
mode,
|
||||
params,
|
||||
x,
|
||||
y
|
||||
);
|
||||
const resultData = await this.enhancedManager.applyLiquify(targetObject, mode, params, x, y);
|
||||
|
||||
// 确保返回结果数据
|
||||
if (!resultData) {
|
||||
console.warn("液化变形没有返回结果数据");
|
||||
} else {
|
||||
console.log(
|
||||
"✅ 液化变形成功,返回图像数据尺寸:",
|
||||
resultData.width,
|
||||
"x",
|
||||
resultData.height
|
||||
);
|
||||
console.log("✅ 液化变形成功,返回图像数据尺寸:", resultData.width, "x", resultData.height);
|
||||
}
|
||||
|
||||
return resultData;
|
||||
|
||||
@@ -159,9 +159,7 @@ export class LiquifyRealTimeUpdater {
|
||||
// });
|
||||
// }
|
||||
} else {
|
||||
console.warn(
|
||||
"=================快速更新液化效果时,图像数据未变化,跳过更新"
|
||||
);
|
||||
console.warn("=================快速更新液化效果时,图像数据未变化,跳过更新");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("快速更新液化效果失败:", error);
|
||||
@@ -184,6 +182,8 @@ export class LiquifyRealTimeUpdater {
|
||||
*/
|
||||
async _fullUpdate(imageData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 临时禁用画布自动渲染
|
||||
const oldRenderOnAddRemove = this.canvas.renderOnAddRemove;
|
||||
try {
|
||||
// 使用高质量canvas进行最终渲染
|
||||
this.highQualityCtx.putImageData(imageData, 0, 0);
|
||||
@@ -233,9 +233,6 @@ export class LiquifyRealTimeUpdater {
|
||||
selected: false,
|
||||
evented: originalObj.evented,
|
||||
});
|
||||
|
||||
// 临时禁用画布自动渲染
|
||||
const oldRenderOnAddRemove = this.canvas.renderOnAddRemove;
|
||||
this.canvas.renderOnAddRemove = false;
|
||||
|
||||
// 智能查找和替换canvas上的对象
|
||||
@@ -244,9 +241,7 @@ export class LiquifyRealTimeUpdater {
|
||||
|
||||
// 如果直接查找失败,尝试通过ID查找
|
||||
if (targetIndex === -1 && originalObjId) {
|
||||
targetIndex = allObjects.findIndex(
|
||||
(obj) => obj.id === originalObjId
|
||||
);
|
||||
targetIndex = allObjects.findIndex((obj) => obj.id === originalObjId);
|
||||
if (targetIndex !== -1) {
|
||||
console.log(`通过ID找到目标对象: ${originalObjId}`);
|
||||
// 更新目标对象引用
|
||||
@@ -256,9 +251,7 @@ export class LiquifyRealTimeUpdater {
|
||||
|
||||
// 如果通过ID查找仍然失败,尝试通过图层ID查找
|
||||
if (targetIndex === -1 && originalObjLayerId) {
|
||||
targetIndex = allObjects.findIndex(
|
||||
(obj) => obj.layerId === originalObjLayerId
|
||||
);
|
||||
targetIndex = allObjects.findIndex((obj) => obj.layerId === originalObjLayerId);
|
||||
if (targetIndex !== -1) {
|
||||
console.log(`通过图层ID找到目标对象: ${originalObjLayerId}`);
|
||||
// 更新目标对象引用
|
||||
@@ -284,9 +277,7 @@ export class LiquifyRealTimeUpdater {
|
||||
resolve(newImg);
|
||||
} else {
|
||||
// 如果在画布中找不到对象,可能对象已被移除或引用已更新
|
||||
console.warn(
|
||||
"在画布中找不到目标对象,可能已被其他操作移除或替换"
|
||||
);
|
||||
console.warn("在画布中找不到目标对象,可能已被其他操作移除或替换");
|
||||
|
||||
// 恢复自动渲染设置
|
||||
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对象
|
||||
@@ -388,13 +361,6 @@ export class LiquifyRealTimeUpdater {
|
||||
console.log("✅ 恢复正常渲染模式");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前目标对象
|
||||
*/
|
||||
getTargetObject() {
|
||||
return this.targetObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图像质量
|
||||
* @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;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
|
||||
@@ -30,10 +30,7 @@ export class LiquifyStateManager {
|
||||
// 设备性能检测
|
||||
this.devicePerformance = this._detectDevicePerformance();
|
||||
|
||||
console.log(
|
||||
"🎯 液化状态管理器已初始化,设备性能等级:",
|
||||
this.devicePerformance
|
||||
);
|
||||
console.log("🎯 液化状态管理器已初始化,设备性能等级:", this.devicePerformance);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,17 +126,10 @@ export class LiquifyStateManager {
|
||||
this.performanceMetrics.totalOperations++;
|
||||
this.performanceMetrics.totalTime += operationTime;
|
||||
this.performanceMetrics.averageTime =
|
||||
this.performanceMetrics.totalTime /
|
||||
this.performanceMetrics.totalOperations;
|
||||
this.performanceMetrics.totalTime / this.performanceMetrics.totalOperations;
|
||||
|
||||
this.performanceMetrics.maxTime = Math.max(
|
||||
this.performanceMetrics.maxTime,
|
||||
operationTime
|
||||
);
|
||||
this.performanceMetrics.minTime = Math.min(
|
||||
this.performanceMetrics.minTime,
|
||||
operationTime
|
||||
);
|
||||
this.performanceMetrics.maxTime = Math.max(this.performanceMetrics.maxTime, operationTime);
|
||||
this.performanceMetrics.minTime = Math.min(this.performanceMetrics.minTime, operationTime);
|
||||
this.performanceMetrics.lastOperationTime = operationTime;
|
||||
}
|
||||
|
||||
@@ -148,15 +138,8 @@ export class LiquifyStateManager {
|
||||
* @param {Object} metrics 性能指标对象
|
||||
*/
|
||||
recordOperationMetrics(metrics) {
|
||||
const {
|
||||
operationTime,
|
||||
operationType,
|
||||
mode,
|
||||
coordinates,
|
||||
imageSize,
|
||||
renderMode,
|
||||
isRealTime,
|
||||
} = metrics;
|
||||
const { operationTime, operationType, mode, coordinates, imageSize, renderMode, isRealTime } =
|
||||
metrics;
|
||||
|
||||
// 记录基础性能数据
|
||||
this.recordDeformation(operationTime);
|
||||
@@ -195,9 +178,7 @@ export class LiquifyStateManager {
|
||||
// 降低图像质量
|
||||
const currentQuality = this.realtimeUpdater.config.imageQuality || 1.0;
|
||||
if (currentQuality > 0.7) {
|
||||
this.realtimeUpdater.setImageQuality(
|
||||
Math.max(0.7, currentQuality - 0.1)
|
||||
);
|
||||
this.realtimeUpdater.setImageQuality(Math.max(0.7, currentQuality - 0.1));
|
||||
console.log("⚡ 自动降低图像质量以提升性能");
|
||||
}
|
||||
|
||||
@@ -215,9 +196,7 @@ export class LiquifyStateManager {
|
||||
if (operationTime < 20 && this.devicePerformance === "high") {
|
||||
const currentQuality = this.realtimeUpdater.config.imageQuality || 1.0;
|
||||
if (currentQuality < 1.0) {
|
||||
this.realtimeUpdater.setImageQuality(
|
||||
Math.min(1.0, currentQuality + 0.05)
|
||||
);
|
||||
this.realtimeUpdater.setImageQuality(Math.min(1.0, currentQuality + 0.05));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,8 +354,7 @@ export class LiquifyStateManager {
|
||||
// 恢复原始设置
|
||||
this.canvas.renderOnAddRemove = this._originalSettings.renderOnAddRemove;
|
||||
this.canvas.skipOffscreen = this._originalSettings.skipOffscreen;
|
||||
this.canvas.enableRetinaScaling =
|
||||
this._originalSettings.enableRetinaScaling;
|
||||
this.canvas.enableRetinaScaling = this._originalSettings.enableRetinaScaling;
|
||||
|
||||
this._originalSettings = null;
|
||||
}
|
||||
@@ -389,14 +367,8 @@ export class LiquifyStateManager {
|
||||
this.performanceMetrics.averageTime =
|
||||
this.performanceMetrics.totalTime / (this.operationCount + 1);
|
||||
|
||||
this.performanceMetrics.maxTime = Math.max(
|
||||
this.performanceMetrics.maxTime,
|
||||
operationTime
|
||||
);
|
||||
this.performanceMetrics.minTime = Math.min(
|
||||
this.performanceMetrics.minTime,
|
||||
operationTime
|
||||
);
|
||||
this.performanceMetrics.maxTime = Math.max(this.performanceMetrics.maxTime, operationTime);
|
||||
this.performanceMetrics.minTime = Math.min(this.performanceMetrics.minTime, operationTime);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,8 +61,7 @@ export class LiquifyWebGLManager {
|
||||
static isSupported() {
|
||||
try {
|
||||
const canvas = document.createElement("canvas");
|
||||
const gl =
|
||||
canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
||||
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
||||
return !!gl;
|
||||
} catch (e) {
|
||||
return false;
|
||||
@@ -76,9 +75,7 @@ export class LiquifyWebGLManager {
|
||||
try {
|
||||
// 创建WebGL画布
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.gl =
|
||||
this.canvas.getContext("webgl") ||
|
||||
this.canvas.getContext("experimental-webgl");
|
||||
this.gl = this.canvas.getContext("webgl") || this.canvas.getContext("experimental-webgl");
|
||||
|
||||
if (!this.gl) {
|
||||
throw new Error("WebGL不可用");
|
||||
@@ -200,10 +197,7 @@ export class LiquifyWebGLManager {
|
||||
}
|
||||
`;
|
||||
|
||||
this.program = this._createProgram(
|
||||
vertexShaderSource,
|
||||
fragmentShaderSource
|
||||
);
|
||||
this.program = this._createProgram(vertexShaderSource, fragmentShaderSource);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,10 +222,7 @@ export class LiquifyWebGLManager {
|
||||
_createProgram(vertexSource, fragmentSource) {
|
||||
const gl = this.gl;
|
||||
const vertexShader = this._createShader(gl.VERTEX_SHADER, vertexSource);
|
||||
const fragmentShader = this._createShader(
|
||||
gl.FRAGMENT_SHADER,
|
||||
fragmentSource
|
||||
);
|
||||
const fragmentShader = this._createShader(gl.FRAGMENT_SHADER, fragmentSource);
|
||||
|
||||
const program = gl.createProgram();
|
||||
gl.attachShader(program, vertexShader);
|
||||
@@ -267,9 +258,7 @@ export class LiquifyWebGLManager {
|
||||
*/
|
||||
_createMeshBuffer() {
|
||||
const gl = this.gl;
|
||||
const vertices = new Float32Array([
|
||||
-1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 1, 1,
|
||||
]);
|
||||
const vertices = new Float32Array([-1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 1, 1]);
|
||||
|
||||
this.meshBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.meshBuffer);
|
||||
@@ -427,12 +416,8 @@ export class LiquifyWebGLManager {
|
||||
}
|
||||
|
||||
// 计算拖拽向量(用于推拉模式)
|
||||
const dragX = this.isDragging
|
||||
? (x - this.initialMouseX) / this.canvas.width
|
||||
: 0;
|
||||
const dragY = this.isDragging
|
||||
? -(y - this.initialMouseY) / this.canvas.height
|
||||
: 0;
|
||||
const dragX = this.isDragging ? (x - this.initialMouseX) / this.canvas.width : 0;
|
||||
const dragY = this.isDragging ? -(y - this.initialMouseY) / this.canvas.height : 0;
|
||||
|
||||
// 获取模式索引
|
||||
const modeIndex = Object.values(this.modes).indexOf(this.currentMode);
|
||||
@@ -466,22 +451,13 @@ export class LiquifyWebGLManager {
|
||||
|
||||
// 读取结果并转换为ImageData
|
||||
const pixels = new Uint8Array(this.canvas.width * this.canvas.height * 4);
|
||||
gl.readPixels(
|
||||
0,
|
||||
0,
|
||||
this.canvas.width,
|
||||
this.canvas.height,
|
||||
gl.RGBA,
|
||||
gl.UNSIGNED_BYTE,
|
||||
pixels
|
||||
);
|
||||
gl.readPixels(0, 0, this.canvas.width, this.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
||||
|
||||
// 翻转Y轴以匹配ImageData格式
|
||||
const flippedPixels = new Uint8ClampedArray(pixels.length);
|
||||
for (let y = 0; y < this.canvas.height; y++) {
|
||||
for (let x = 0; x < this.canvas.width; x++) {
|
||||
const srcIndex =
|
||||
((this.canvas.height - 1 - y) * this.canvas.width + x) * 4;
|
||||
const srcIndex = ((this.canvas.height - 1 - y) * this.canvas.width + x) * 4;
|
||||
const dstIndex = (y * this.canvas.width + x) * 4;
|
||||
flippedPixels[dstIndex] = pixels[srcIndex];
|
||||
flippedPixels[dstIndex + 1] = pixels[srcIndex + 1];
|
||||
@@ -503,16 +479,7 @@ export class LiquifyWebGLManager {
|
||||
|
||||
// 将原始纹理复制到当前纹理
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.currentTexture);
|
||||
gl.copyTexImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
gl.RGBA,
|
||||
0,
|
||||
0,
|
||||
this.canvas.width,
|
||||
this.canvas.height,
|
||||
0
|
||||
);
|
||||
gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, this.canvas.width, this.canvas.height, 0);
|
||||
|
||||
// 读取原始纹理数据
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
||||
@@ -525,21 +492,9 @@ export class LiquifyWebGLManager {
|
||||
);
|
||||
|
||||
const pixels = new Uint8Array(this.canvas.width * this.canvas.height * 4);
|
||||
gl.readPixels(
|
||||
0,
|
||||
0,
|
||||
this.canvas.width,
|
||||
this.canvas.height,
|
||||
gl.RGBA,
|
||||
gl.UNSIGNED_BYTE,
|
||||
pixels
|
||||
);
|
||||
gl.readPixels(0, 0, this.canvas.width, this.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
||||
|
||||
return new ImageData(
|
||||
new Uint8ClampedArray(pixels),
|
||||
this.canvas.width,
|
||||
this.canvas.height
|
||||
);
|
||||
return new ImageData(new Uint8ClampedArray(pixels), this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -144,10 +144,7 @@ export class MinimapManager {
|
||||
this.eventHandlers.mouseup = this.handleMinimapMouseUp;
|
||||
// 移除mouseout事件处理,允许拖动操作持续到鼠标释放
|
||||
|
||||
this.minimapCanvas.addEventListener(
|
||||
"mousedown",
|
||||
this.eventHandlers.mousedown
|
||||
);
|
||||
this.minimapCanvas.addEventListener("mousedown", this.eventHandlers.mousedown);
|
||||
document.addEventListener("mousemove", this.eventHandlers.mousemove);
|
||||
document.addEventListener("mouseup", this.eventHandlers.mouseup);
|
||||
// 移除mouseout事件监听
|
||||
@@ -177,10 +174,7 @@ export class MinimapManager {
|
||||
|
||||
this.eventHandlers.touchend = this.handleMinimapMouseUp;
|
||||
|
||||
this.minimapCanvas.addEventListener(
|
||||
"touchstart",
|
||||
this.eventHandlers.touchstart
|
||||
);
|
||||
this.minimapCanvas.addEventListener("touchstart", this.eventHandlers.touchstart);
|
||||
document.addEventListener("touchmove", this.eventHandlers.touchmove, {
|
||||
passive: false,
|
||||
});
|
||||
@@ -204,18 +198,12 @@ export class MinimapManager {
|
||||
this.mainCanvas.off("object:rotating", this.handleMainCanvasChange);
|
||||
|
||||
// 移除鼠标事件监听
|
||||
this.minimapCanvas.removeEventListener(
|
||||
"mousedown",
|
||||
this.eventHandlers.mousedown
|
||||
);
|
||||
this.minimapCanvas.removeEventListener("mousedown", this.eventHandlers.mousedown);
|
||||
document.removeEventListener("mousemove", this.eventHandlers.mousemove);
|
||||
document.removeEventListener("mouseup", this.eventHandlers.mouseup);
|
||||
|
||||
// 移除触摸事件监听
|
||||
this.minimapCanvas.removeEventListener(
|
||||
"touchstart",
|
||||
this.eventHandlers.touchstart
|
||||
);
|
||||
this.minimapCanvas.removeEventListener("touchstart", this.eventHandlers.touchstart);
|
||||
document.removeEventListener("touchmove", this.eventHandlers.touchmove);
|
||||
document.removeEventListener("touchend", this.eventHandlers.touchend);
|
||||
}
|
||||
@@ -281,8 +269,7 @@ export class MinimapManager {
|
||||
}
|
||||
|
||||
// 添加边距
|
||||
const padding =
|
||||
Math.max(this.mainCanvas.getWidth(), this.mainCanvas.getHeight()) * 0.1;
|
||||
const padding = Math.max(this.mainCanvas.getWidth(), this.mainCanvas.getHeight()) * 0.1;
|
||||
this.contentBounds = {
|
||||
minX: minX - padding,
|
||||
minY: minY - padding,
|
||||
@@ -347,11 +334,7 @@ export class MinimapManager {
|
||||
this.dragStart = { x, y };
|
||||
|
||||
// 移动画布视口
|
||||
this.moveViewport(
|
||||
this.dragStartViewport.x + deltaX,
|
||||
this.dragStartViewport.y + deltaY,
|
||||
false
|
||||
);
|
||||
this.moveViewport(this.dragStartViewport.x + deltaX, this.dragStartViewport.y + deltaY, false);
|
||||
|
||||
// 更新拖拽起始视口位置
|
||||
this.dragStartViewport = this.calculateViewportRect();
|
||||
@@ -391,9 +374,7 @@ export class MinimapManager {
|
||||
viewportHeight = this.lastViewportSize.height;
|
||||
} else {
|
||||
viewportWidth = Math.round((this.mainCanvas.getWidth() / zoom) * scaleX);
|
||||
viewportHeight = Math.round(
|
||||
(this.mainCanvas.getHeight() / zoom) * scaleY
|
||||
);
|
||||
viewportHeight = Math.round((this.mainCanvas.getHeight() / zoom) * scaleY);
|
||||
}
|
||||
|
||||
// 添加边界限制,确保视口不会超出小地图
|
||||
@@ -476,21 +457,11 @@ export class MinimapManager {
|
||||
|
||||
try {
|
||||
// 清空小地图
|
||||
this.minimapCtx.clearRect(
|
||||
0,
|
||||
0,
|
||||
this.minimapSize.width,
|
||||
this.minimapSize.height
|
||||
);
|
||||
this.minimapCtx.clearRect(0, 0, this.minimapSize.width, this.minimapSize.height);
|
||||
|
||||
// 绘制小地图背景
|
||||
this.minimapCtx.fillStyle = this.mainCanvas.backgroundColor || "#f0f0f0";
|
||||
this.minimapCtx.fillRect(
|
||||
0,
|
||||
0,
|
||||
this.minimapSize.width,
|
||||
this.minimapSize.height
|
||||
);
|
||||
this.minimapCtx.fillRect(0, 0, this.minimapSize.width, this.minimapSize.height);
|
||||
|
||||
// 计算内容边界尺寸
|
||||
const contentWidth = this.contentBounds.maxX - this.contentBounds.minX;
|
||||
@@ -654,11 +625,7 @@ export class MinimapManager {
|
||||
this.removeEventListeners();
|
||||
|
||||
// 从DOM中移除canvas
|
||||
if (
|
||||
this.container &&
|
||||
this.minimapCanvas &&
|
||||
this.minimapCanvas.parentNode === this.container
|
||||
) {
|
||||
if (this.container && this.minimapCanvas && this.minimapCanvas.parentNode === this.container) {
|
||||
this.container.removeChild(this.minimapCanvas);
|
||||
}
|
||||
|
||||
@@ -709,12 +676,7 @@ export class MinimapManager {
|
||||
}
|
||||
|
||||
const offCtx = this._offscreenCanvas.getContext("2d");
|
||||
offCtx.clearRect(
|
||||
0,
|
||||
0,
|
||||
this._offscreenCanvas.width,
|
||||
this._offscreenCanvas.height
|
||||
);
|
||||
offCtx.clearRect(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height);
|
||||
|
||||
// 绘制图层内容到离屏画布
|
||||
this._renderLayersToMinimap(offCtx, ratio);
|
||||
@@ -755,8 +717,8 @@ export class MinimapManager {
|
||||
typeof this.canvas.layers.value !== "undefined"
|
||||
? this.canvas.layers.value
|
||||
: Array.isArray(this.canvas.layers)
|
||||
? this.canvas.layers
|
||||
: [];
|
||||
? this.canvas.layers
|
||||
: [];
|
||||
|
||||
// 过滤出可见图层
|
||||
layersArray.forEach((layer) => {
|
||||
@@ -800,10 +762,8 @@ export class MinimapManager {
|
||||
|
||||
const left = fabricObj.left * ratio;
|
||||
const top = fabricObj.top * ratio;
|
||||
const width =
|
||||
(fabricObj.width || 20) * (fabricObj.scaleX || 1) * ratio;
|
||||
const height =
|
||||
(fabricObj.height || 20) * (fabricObj.scaleY || 1) * ratio;
|
||||
const width = (fabricObj.width || 20) * (fabricObj.scaleX || 1) * ratio;
|
||||
const height = (fabricObj.height || 20) * (fabricObj.scaleY || 1) * ratio;
|
||||
|
||||
ctx.fillRect(left, top, width, height);
|
||||
}
|
||||
|
||||
@@ -319,10 +319,7 @@ export class SelectionManager {
|
||||
}
|
||||
|
||||
// 触发选区变化回调
|
||||
if (
|
||||
this.onSelectionChanged &&
|
||||
typeof this.onSelectionChanged === "function"
|
||||
) {
|
||||
if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") {
|
||||
this.onSelectionChanged();
|
||||
}
|
||||
|
||||
@@ -397,10 +394,7 @@ export class SelectionManager {
|
||||
this.featherAmount = 0;
|
||||
|
||||
// 触发选区变化回调
|
||||
if (
|
||||
this.onSelectionChanged &&
|
||||
typeof this.onSelectionChanged === "function"
|
||||
) {
|
||||
if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") {
|
||||
this.onSelectionChanged();
|
||||
}
|
||||
|
||||
@@ -600,8 +594,7 @@ export class SelectionManager {
|
||||
// 添加新的点,但避免添加过于密集的点
|
||||
const lastPoint = this.drawingPoints[this.drawingPoints.length - 1];
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(pointer.x - lastPoint.x, 2) +
|
||||
Math.pow(pointer.y - lastPoint.y, 2)
|
||||
Math.pow(pointer.x - lastPoint.x, 2) + Math.pow(pointer.y - lastPoint.y, 2)
|
||||
);
|
||||
|
||||
// 只有当距离大于2像素时才添加新点,避免路径过于复杂
|
||||
@@ -653,8 +646,7 @@ export class SelectionManager {
|
||||
const firstPoint = this.drawingPoints[0];
|
||||
const lastPoint = this.drawingPoints[this.drawingPoints.length - 1];
|
||||
const closingDistance = Math.sqrt(
|
||||
Math.pow(firstPoint.x - lastPoint.x, 2) +
|
||||
Math.pow(firstPoint.y - lastPoint.y, 2)
|
||||
Math.pow(firstPoint.x - lastPoint.x, 2) + Math.pow(firstPoint.y - lastPoint.y, 2)
|
||||
);
|
||||
|
||||
// 如果首尾距离较大,自动添加闭合线段
|
||||
|
||||
@@ -261,10 +261,7 @@ const actions = {
|
||||
}
|
||||
|
||||
// 如果有当前笔刷实例且有更新方法,则调用
|
||||
if (
|
||||
state.currentBrushInstance &&
|
||||
state.currentBrushInstance.updateProperty
|
||||
) {
|
||||
if (state.currentBrushInstance && state.currentBrushInstance.updateProperty) {
|
||||
state.currentBrushInstance.updateProperty(propId, value);
|
||||
}
|
||||
},
|
||||
@@ -277,7 +274,7 @@ const actions = {
|
||||
*/
|
||||
getPropertyValue(propId, defaultValue) {
|
||||
// 检查属性值是否存在
|
||||
if (state.propertyValues.hasOwnProperty(propId)) {
|
||||
if (Object.prototype.hasOwnProperty.call(state.propertyValues, propId)) {
|
||||
return state.propertyValues[propId];
|
||||
}
|
||||
|
||||
@@ -551,10 +548,7 @@ const actions = {
|
||||
|
||||
// 导入材质预设
|
||||
if (config.texturePresets && Array.isArray(config.texturePresets)) {
|
||||
state.texturePresets = [
|
||||
...state.texturePresets,
|
||||
...config.texturePresets,
|
||||
];
|
||||
state.texturePresets = [...state.texturePresets, ...config.texturePresets];
|
||||
}
|
||||
|
||||
// 导入自定义材质
|
||||
@@ -580,9 +574,7 @@ const actions = {
|
||||
state.texturePresets.forEach((preset, index) => {
|
||||
const texture = texturePresetManager.getTextureById(preset.textureId);
|
||||
if (!texture) {
|
||||
console.warn(
|
||||
`材质预设 "${preset.name}" 引用的材质 ${preset.textureId} 不存在`
|
||||
);
|
||||
console.warn(`材质预设 "${preset.name}" 引用的材质 ${preset.textureId} 不存在`);
|
||||
// 可以选择使用默认材质替换或删除该预设
|
||||
if (texturePresetManager.getAllTextures().length > 0) {
|
||||
preset.textureId = texturePresetManager.getAllTextures()[0].id;
|
||||
@@ -654,9 +646,7 @@ export const BrushStore = {
|
||||
* @returns {Object} {r, g, b, a} 颜色值对象
|
||||
*/
|
||||
parseRGBAColor(rgbaColor) {
|
||||
const rgbaMatch = rgbaColor.match(
|
||||
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/
|
||||
);
|
||||
const rgbaMatch = rgbaColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
||||
if (rgbaMatch) {
|
||||
return {
|
||||
r: parseInt(rgbaMatch[1]),
|
||||
|
||||
@@ -113,23 +113,12 @@ async function testCPUManagerBasics() {
|
||||
// 应用变形
|
||||
const result = manager.applyDeformation(50, 50);
|
||||
|
||||
if (
|
||||
!result ||
|
||||
result.width !== testImageData.width ||
|
||||
result.height !== testImageData.height
|
||||
) {
|
||||
if (!result || result.width !== testImageData.width || result.height !== testImageData.height) {
|
||||
throw new Error("CPU管理器变形结果无效");
|
||||
}
|
||||
|
||||
// 测试不同模式
|
||||
const modes = [
|
||||
"push",
|
||||
"clockwise",
|
||||
"counterclockwise",
|
||||
"pinch",
|
||||
"expand",
|
||||
"reconstruct",
|
||||
];
|
||||
const modes = ["push", "clockwise", "counterclockwise", "pinch", "expand", "reconstruct"];
|
||||
for (const mode of modes) {
|
||||
manager.setMode(mode);
|
||||
const modeResult = manager.applyDeformation(25, 25);
|
||||
@@ -170,11 +159,7 @@ async function testWebGLManagerBasics() {
|
||||
// 应用变形
|
||||
const result = manager.applyDeformation(50, 50);
|
||||
|
||||
if (
|
||||
!result ||
|
||||
result.width !== testImageData.width ||
|
||||
result.height !== testImageData.height
|
||||
) {
|
||||
if (!result || result.width !== testImageData.width || result.height !== testImageData.height) {
|
||||
throw new Error("WebGL管理器变形结果无效");
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -300,11 +285,9 @@ async function testCoordinateConversion() {
|
||||
|
||||
// 转换到图像坐标
|
||||
let imageX =
|
||||
(localX + mockFabricObject.width / 2) *
|
||||
(testImageData.width / mockFabricObject.width);
|
||||
(localX + mockFabricObject.width / 2) * (testImageData.width / mockFabricObject.width);
|
||||
let imageY =
|
||||
(localY + mockFabricObject.height / 2) *
|
||||
(testImageData.height / mockFabricObject.height);
|
||||
(localY + mockFabricObject.height / 2) * (testImageData.height / mockFabricObject.height);
|
||||
|
||||
// 处理翻转
|
||||
if (mockFabricObject.flipX) {
|
||||
@@ -364,9 +347,7 @@ async function testPerformance() {
|
||||
|
||||
// 验证性能阈值(每次操作不应超过50ms)
|
||||
if (avgTime > 50) {
|
||||
throw new Error(
|
||||
`性能不达标:平均操作时间 ${avgTime.toFixed(2)}ms 超过阈值 50ms`
|
||||
);
|
||||
throw new Error(`性能不达标:平均操作时间 ${avgTime.toFixed(2)}ms 超过阈值 50ms`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,9 +355,7 @@ async function testPerformance() {
|
||||
* 测试内存管理
|
||||
*/
|
||||
async function testMemoryManagement() {
|
||||
const initialMemory = performance.memory
|
||||
? performance.memory.usedJSHeapSize
|
||||
: 0;
|
||||
const initialMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
|
||||
|
||||
// 创建多个管理器实例
|
||||
const managers = [];
|
||||
@@ -399,9 +378,7 @@ async function testMemoryManagement() {
|
||||
window.gc();
|
||||
}
|
||||
|
||||
const finalMemory = performance.memory
|
||||
? performance.memory.usedJSHeapSize
|
||||
: 0;
|
||||
const finalMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
|
||||
const memoryIncrease = finalMemory - initialMemory;
|
||||
|
||||
console.log(
|
||||
@@ -414,9 +391,7 @@ async function testMemoryManagement() {
|
||||
|
||||
// 验证内存增长不超过10MB(基本的内存泄漏检查)
|
||||
if (memoryIncrease > 10 * 1024 * 1024) {
|
||||
console.warn(
|
||||
`潜在内存泄漏: 内存增长 ${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`
|
||||
);
|
||||
console.warn(`潜在内存泄漏: 内存增长 ${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,12 +429,7 @@ export async function runLiquifyIntegrationTests() {
|
||||
console.log(`总测试数: ${testResults.totalTests}`);
|
||||
console.log(`通过: ${testResults.passedTests}`);
|
||||
console.log(`失败: ${testResults.failedTests}`);
|
||||
console.log(
|
||||
`成功率: ${(
|
||||
(testResults.passedTests / testResults.totalTests) *
|
||||
100
|
||||
).toFixed(1)}%`
|
||||
);
|
||||
console.log(`成功率: ${((testResults.passedTests / testResults.totalTests) * 100).toFixed(1)}%`);
|
||||
|
||||
if (testResults.errors.length > 0) {
|
||||
console.log("\n❌ 失败的测试:");
|
||||
|
||||
@@ -45,19 +45,13 @@ export function testCoordinateConversion() {
|
||||
|
||||
// 模拟转换结果
|
||||
const expectedImageX =
|
||||
((coord.x - mockFabricObject.left) / mockFabricObject.scaleX +
|
||||
mockFabricObject.width / 2) *
|
||||
((coord.x - mockFabricObject.left) / mockFabricObject.scaleX + mockFabricObject.width / 2) *
|
||||
(mockImageData.width / mockFabricObject.width);
|
||||
const expectedImageY =
|
||||
((coord.y - mockFabricObject.top) / mockFabricObject.scaleY +
|
||||
mockFabricObject.height / 2) *
|
||||
((coord.y - mockFabricObject.top) / mockFabricObject.scaleY + mockFabricObject.height / 2) *
|
||||
(mockImageData.height / mockFabricObject.height);
|
||||
|
||||
console.log(
|
||||
` 预期图像坐标: (${expectedImageX.toFixed(2)}, ${expectedImageY.toFixed(
|
||||
2
|
||||
)})`
|
||||
);
|
||||
console.log(` 预期图像坐标: (${expectedImageX.toFixed(2)}, ${expectedImageY.toFixed(2)})`);
|
||||
});
|
||||
|
||||
console.log("坐标转换测试完成");
|
||||
@@ -83,10 +77,7 @@ export function testPushAlgorithmPerformance() {
|
||||
// 模拟液化计算
|
||||
const movementLength =
|
||||
i > 0
|
||||
? Math.sqrt(
|
||||
Math.pow(x - movements[i - 1].x, 2) +
|
||||
Math.pow(y - movements[i - 1].y, 2)
|
||||
)
|
||||
? Math.sqrt(Math.pow(x - movements[i - 1].x, 2) + Math.pow(y - movements[i - 1].y, 2))
|
||||
: 0;
|
||||
|
||||
if (movementLength > 0.5) {
|
||||
@@ -151,12 +142,8 @@ export function testScaleConsistency() {
|
||||
const localX = (canvasCoord.x - fabricObject.left) / fabricObject.scaleX;
|
||||
const localY = (canvasCoord.y - fabricObject.top) / fabricObject.scaleY;
|
||||
|
||||
const imageX =
|
||||
(localX + fabricObject.width / 2) *
|
||||
(imageData.width / fabricObject.width);
|
||||
const imageY =
|
||||
(localY + fabricObject.height / 2) *
|
||||
(imageData.height / fabricObject.height);
|
||||
const imageX = (localX + fabricObject.width / 2) * (imageData.width / fabricObject.width);
|
||||
const imageY = (localY + fabricObject.height / 2) * (imageData.height / fabricObject.height);
|
||||
|
||||
console.log(` 画布坐标: (${canvasCoord.x}, ${canvasCoord.y})`);
|
||||
console.log(` 本地坐标: (${localX.toFixed(2)}, ${localY.toFixed(2)})`);
|
||||
@@ -164,10 +151,7 @@ export function testScaleConsistency() {
|
||||
|
||||
// 验证图像坐标是否在合理范围内
|
||||
const isValid =
|
||||
imageX >= 0 &&
|
||||
imageX < imageData.width &&
|
||||
imageY >= 0 &&
|
||||
imageY < imageData.height;
|
||||
imageX >= 0 && imageX < imageData.width && imageY >= 0 && imageY < imageData.height;
|
||||
console.log(` 坐标有效性: ${isValid ? "✓" : "✗"}`);
|
||||
});
|
||||
|
||||
@@ -190,10 +174,7 @@ export function runAllTests() {
|
||||
|
||||
console.log("=== 液化功能测试完成 ===");
|
||||
console.log(
|
||||
`推荐配置: 节流时间 ${Math.max(
|
||||
16,
|
||||
perfResult.avgTimePerOperation * 2
|
||||
).toFixed(0)}ms`
|
||||
`推荐配置: 节流时间 ${Math.max(16, perfResult.avgTimePerOperation * 2).toFixed(0)}ms`
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -81,11 +81,7 @@ export class LayerSort {
|
||||
zIndexMap.set(layer.fabricObject.id, currentZIndex++);
|
||||
} else if (!layer.isBackground && !layer.isFixed) {
|
||||
// 普通图层
|
||||
currentZIndex = this.processLayerObjects(
|
||||
layer,
|
||||
currentZIndex,
|
||||
zIndexMap
|
||||
);
|
||||
currentZIndex = this.processLayerObjects(layer, currentZIndex, zIndexMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,8 +138,7 @@ export class LayerSort {
|
||||
getChildLayersInOrder(parentLayerId) {
|
||||
// 获取所有子图层
|
||||
const childLayers =
|
||||
this.layers.value.filter((layer) => layer.id === parentLayerId)
|
||||
?.children || [];
|
||||
this.layers.value.filter((layer) => layer.id === parentLayerId)?.children || [];
|
||||
|
||||
return childLayers;
|
||||
}
|
||||
@@ -194,9 +189,7 @@ export class LayerSort {
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
const item = sortedObjects[i];
|
||||
const currentIndex = this.canvas
|
||||
.getObjects()
|
||||
.indexOf(item.object);
|
||||
const currentIndex = this.canvas.getObjects().indexOf(item.object);
|
||||
if (currentIndex !== i && currentIndex !== -1) {
|
||||
this.canvas.moveTo(item.object, i);
|
||||
}
|
||||
@@ -288,23 +281,17 @@ export class LayerSort {
|
||||
return this.layers.value.length; // 背景图层插入到最后
|
||||
} else if (newLayer.isFixed) {
|
||||
// 固定图层插入到背景图层之前
|
||||
const bgIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const bgIndex = this.layers.value.findIndex((layer) => layer.isBackground);
|
||||
return bgIndex !== -1 ? bgIndex : this.layers.value.length;
|
||||
} else {
|
||||
// 普通图层插入到固定图层之前
|
||||
const fixedIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.isFixed
|
||||
);
|
||||
const fixedIndex = this.layers.value.findIndex((layer) => layer.isFixed);
|
||||
return fixedIndex !== -1 ? fixedIndex : this.layers.value.length;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果指定了目标图层,插入到目标图层之前
|
||||
const targetIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.id === targetLayerId
|
||||
);
|
||||
const targetIndex = this.layers.value.findIndex((layer) => layer.id === targetLayerId);
|
||||
return targetIndex !== -1 ? targetIndex : this.layers.value.length;
|
||||
}
|
||||
|
||||
@@ -532,9 +519,7 @@ export class LayerSort {
|
||||
async smartSort(targetLayerIds = null) {
|
||||
const layersToSort = targetLayerIds
|
||||
? this.layers.value.filter((layer) => targetLayerIds.includes(layer.id))
|
||||
: this.layers.value.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
);
|
||||
: this.layers.value.filter((layer) => !layer.isBackground && !layer.isFixed);
|
||||
|
||||
if (layersToSort.length <= 1) return true;
|
||||
|
||||
@@ -556,9 +541,7 @@ export class LayerSort {
|
||||
|
||||
// 更新图层顺序
|
||||
const sortedLayerIds = layersToSort.map((layer) => layer.id);
|
||||
const otherLayers = this.layers.value.filter(
|
||||
(layer) => !sortedLayerIds.includes(layer.id)
|
||||
);
|
||||
const otherLayers = this.layers.value.filter((layer) => !sortedLayerIds.includes(layer.id));
|
||||
|
||||
// 重新组织图层数组:保持背景层和固定层的位置
|
||||
const newLayers = [];
|
||||
@@ -733,12 +716,9 @@ export const LayerSortUtils = {
|
||||
* @returns {number} 排序权重
|
||||
*/
|
||||
getLayerSortWeight(layer) {
|
||||
if (layer.isBackground)
|
||||
return LayerSortConstants.LAYER_PRIORITY[LayerType.BACKGROUND];
|
||||
if (layer.isFixed)
|
||||
return LayerSortConstants.LAYER_PRIORITY[LayerType.FIXED];
|
||||
if (layer.children?.length > 0)
|
||||
return LayerSortConstants.LAYER_PRIORITY[LayerType.GROUP];
|
||||
if (layer.isBackground) return LayerSortConstants.LAYER_PRIORITY[LayerType.BACKGROUND];
|
||||
if (layer.isFixed) return LayerSortConstants.LAYER_PRIORITY[LayerType.FIXED];
|
||||
if (layer.children?.length > 0) return LayerSortConstants.LAYER_PRIORITY[LayerType.GROUP];
|
||||
return LayerSortConstants.LAYER_PRIORITY[LayerType.NORMAL];
|
||||
},
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ export const createCanvas = (elementId, options = {}) => {
|
||||
const canvas = new fabric.Canvas(elementId, {
|
||||
enableRetinaScaling: canvasConfig.enableRetinaScaling,
|
||||
renderOnAddRemove: false,
|
||||
enableRetinaScaling: true,
|
||||
preserveObjectStacking: true, // 保持对象堆叠顺序
|
||||
// skipOffscreen: true, // 跳过离屏渲染
|
||||
imageSmoothingEnabled: true, // 启用图像平滑 - 抗锯齿
|
||||
|
||||
@@ -6,12 +6,7 @@ export function deepCompare(obj1, obj2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
obj1 === null ||
|
||||
obj2 === null ||
|
||||
typeof obj1 !== "object" ||
|
||||
typeof obj2 !== "object"
|
||||
) {
|
||||
if (obj1 === null || obj2 === null || typeof obj1 !== "object" || typeof obj2 !== "object") {
|
||||
return { _value: obj2, _oldValue: obj1 };
|
||||
}
|
||||
|
||||
@@ -78,7 +73,7 @@ export function deepClone(obj) {
|
||||
if (typeof obj === "object") {
|
||||
const cloned = {};
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
cloned[key] = deepClone(obj[key]);
|
||||
}
|
||||
}
|
||||
@@ -228,11 +223,7 @@ export function formatDuration(milliseconds) {
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
export function isValidCommand(command) {
|
||||
return (
|
||||
command &&
|
||||
typeof command === "object" &&
|
||||
typeof command.execute === "function"
|
||||
);
|
||||
return command && typeof command === "object" && typeof command.execute === "function";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,7 +277,7 @@ export function getObjectDepth(obj) {
|
||||
|
||||
let maxDepth = 0;
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const depth = getObjectDepth(obj[key]);
|
||||
maxDepth = Math.max(maxDepth, depth);
|
||||
}
|
||||
@@ -313,8 +304,7 @@ export function checkBrowserSupport() {
|
||||
return {
|
||||
WeakRef: typeof WeakRef !== "undefined",
|
||||
FinalizationRegistry: typeof FinalizationRegistry !== "undefined",
|
||||
PerformanceMemory:
|
||||
typeof performance !== "undefined" && !!performance.memory,
|
||||
PerformanceMemory: typeof performance !== "undefined" && !!performance.memory,
|
||||
RequestIdleCallback: typeof requestIdleCallback !== "undefined",
|
||||
IntersectionObserver: typeof IntersectionObserver !== "undefined",
|
||||
ResizeObserver: typeof ResizeObserver !== "undefined",
|
||||
@@ -337,12 +327,7 @@ export function delay(ms) {
|
||||
* @returns {Promise} 执行结果
|
||||
*/
|
||||
export async function retry(fn, options = {}) {
|
||||
const {
|
||||
retries = 3,
|
||||
delay: delayMs = 1000,
|
||||
backoff = 1.5,
|
||||
shouldRetry = () => true,
|
||||
} = options;
|
||||
const { retries = 3, delay: delayMs = 1000, backoff = 1.5, shouldRetry = () => true } = options;
|
||||
|
||||
let attempt = 0;
|
||||
let currentDelay = delayMs;
|
||||
@@ -377,9 +362,7 @@ export async function batchProcess(items, processor, options = {}) {
|
||||
|
||||
for (let i = 0; i < items.length; i += batchSize) {
|
||||
const batch = items.slice(i, i + batchSize);
|
||||
const batchResults = await Promise.all(
|
||||
batch.map((item) => processor(item))
|
||||
);
|
||||
const batchResults = await Promise.all(batch.map((item) => processor(item)));
|
||||
|
||||
results.push(...batchResults);
|
||||
|
||||
|
||||
@@ -35,14 +35,8 @@ function initAligningGuidelines(canvas) {
|
||||
ctx.lineWidth = aligningLineWidth;
|
||||
ctx.strokeStyle = aligningLineColor;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(
|
||||
x1 * zoom + viewportTransform[4],
|
||||
y1 * zoom + viewportTransform[5]
|
||||
);
|
||||
ctx.lineTo(
|
||||
x2 * zoom + viewportTransform[4],
|
||||
y2 * zoom + viewportTransform[5]
|
||||
);
|
||||
ctx.moveTo(x1 * zoom + viewportTransform[4], y1 * zoom + viewportTransform[5]);
|
||||
ctx.lineTo(x2 * zoom + viewportTransform[4], y2 * zoom + viewportTransform[5]);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -50,11 +44,7 @@ function initAligningGuidelines(canvas) {
|
||||
function isInRange(value1, value2) {
|
||||
value1 = Math.round(value1);
|
||||
value2 = Math.round(value2);
|
||||
for (
|
||||
var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin;
|
||||
i <= len;
|
||||
i++
|
||||
) {
|
||||
for (var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin; i <= len; i++) {
|
||||
if (i === value2) {
|
||||
return true;
|
||||
}
|
||||
@@ -77,8 +67,7 @@ function initAligningGuidelines(canvas) {
|
||||
activeObjectLeft = activeObjectCenter.x,
|
||||
activeObjectTop = activeObjectCenter.y,
|
||||
activeObjectBoundingRect = activeObject.getBoundingRect(),
|
||||
activeObjectHeight =
|
||||
activeObjectBoundingRect.height / viewportTransform[3],
|
||||
activeObjectHeight = activeObjectBoundingRect.height / viewportTransform[3],
|
||||
activeObjectWidth = activeObjectBoundingRect.width / viewportTransform[0],
|
||||
horizontalInTheRange = false,
|
||||
verticalInTheRange = false,
|
||||
@@ -100,12 +89,7 @@ function initAligningGuidelines(canvas) {
|
||||
objectWidth = objectBoundingRect.width / viewportTransform[0];
|
||||
|
||||
// snaps if the right side of the active object touches the left side of the object
|
||||
if (
|
||||
isInRange(
|
||||
activeObjectLeft + activeObjectWidth / 2,
|
||||
objectLeft - objectWidth / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(activeObjectLeft + activeObjectWidth / 2, objectLeft - objectWidth / 2)) {
|
||||
verticalInTheRange = true;
|
||||
verticalLines.push({
|
||||
x: objectLeft - objectWidth / 2,
|
||||
@@ -120,22 +104,14 @@ function initAligningGuidelines(canvas) {
|
||||
});
|
||||
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
objectLeft - objectWidth / 2 - activeObjectWidth / 2,
|
||||
activeObjectTop
|
||||
),
|
||||
new fabric.Point(objectLeft - objectWidth / 2 - activeObjectWidth / 2, activeObjectTop),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
}
|
||||
|
||||
// snaps if the left side of the active object touches the right side of the object
|
||||
if (
|
||||
isInRange(
|
||||
activeObjectLeft - activeObjectWidth / 2,
|
||||
objectLeft + objectWidth / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(activeObjectLeft - activeObjectWidth / 2, objectLeft + objectWidth / 2)) {
|
||||
verticalInTheRange = true;
|
||||
verticalLines.push({
|
||||
x: objectLeft + objectWidth / 2,
|
||||
@@ -150,22 +126,14 @@ function initAligningGuidelines(canvas) {
|
||||
});
|
||||
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
objectLeft + objectWidth / 2 + activeObjectWidth / 2,
|
||||
activeObjectTop
|
||||
),
|
||||
new fabric.Point(objectLeft + objectWidth / 2 + activeObjectWidth / 2, activeObjectTop),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
}
|
||||
|
||||
// snaps if the bottom of the object touches the top of the active object
|
||||
if (
|
||||
isInRange(
|
||||
objectTop + objectHeight / 2,
|
||||
activeObjectTop - activeObjectHeight / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(objectTop + objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
|
||||
horizontalInTheRange = true;
|
||||
horizontalLines.push({
|
||||
y: objectTop + objectHeight / 2,
|
||||
@@ -180,22 +148,14 @@ function initAligningGuidelines(canvas) {
|
||||
});
|
||||
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
activeObjectLeft,
|
||||
objectTop + objectHeight / 2 + activeObjectHeight / 2
|
||||
),
|
||||
new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 + activeObjectHeight / 2),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
}
|
||||
|
||||
// snaps if the top of the object touches the bottom of the active object
|
||||
if (
|
||||
isInRange(
|
||||
objectTop - objectHeight / 2,
|
||||
activeObjectTop + activeObjectHeight / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(objectTop - objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
|
||||
horizontalInTheRange = true;
|
||||
horizontalLines.push({
|
||||
y: objectTop - objectHeight / 2,
|
||||
@@ -210,10 +170,7 @@ function initAligningGuidelines(canvas) {
|
||||
});
|
||||
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
activeObjectLeft,
|
||||
objectTop - objectHeight / 2 - activeObjectHeight / 2
|
||||
),
|
||||
new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 - activeObjectHeight / 2),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
@@ -241,12 +198,7 @@ function initAligningGuidelines(canvas) {
|
||||
}
|
||||
|
||||
// snap by the left edge
|
||||
if (
|
||||
isInRange(
|
||||
objectLeft - objectWidth / 2,
|
||||
activeObjectLeft - activeObjectWidth / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) {
|
||||
verticalInTheRange = true;
|
||||
verticalLines.push({
|
||||
x: objectLeft - objectWidth / 2,
|
||||
@@ -260,22 +212,14 @@ function initAligningGuidelines(canvas) {
|
||||
: activeObjectTop - activeObjectHeight / 2 - aligningLineOffset,
|
||||
});
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
objectLeft - objectWidth / 2 + activeObjectWidth / 2,
|
||||
activeObjectTop
|
||||
),
|
||||
new fabric.Point(objectLeft - objectWidth / 2 + activeObjectWidth / 2, activeObjectTop),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
}
|
||||
|
||||
// snap by the right edge
|
||||
if (
|
||||
isInRange(
|
||||
objectLeft + objectWidth / 2,
|
||||
activeObjectLeft + activeObjectWidth / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) {
|
||||
verticalInTheRange = true;
|
||||
verticalLines.push({
|
||||
x: objectLeft + objectWidth / 2,
|
||||
@@ -289,10 +233,7 @@ function initAligningGuidelines(canvas) {
|
||||
: activeObjectTop - activeObjectHeight / 2 - aligningLineOffset,
|
||||
});
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
objectLeft + objectWidth / 2 - activeObjectWidth / 2,
|
||||
activeObjectTop
|
||||
),
|
||||
new fabric.Point(objectLeft + objectWidth / 2 - activeObjectWidth / 2, activeObjectTop),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
@@ -320,12 +261,7 @@ function initAligningGuidelines(canvas) {
|
||||
}
|
||||
|
||||
// snap by the top edge
|
||||
if (
|
||||
isInRange(
|
||||
objectTop - objectHeight / 2,
|
||||
activeObjectTop - activeObjectHeight / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
|
||||
horizontalInTheRange = true;
|
||||
horizontalLines.push({
|
||||
y: objectTop - objectHeight / 2,
|
||||
@@ -339,22 +275,14 @@ function initAligningGuidelines(canvas) {
|
||||
: activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset,
|
||||
});
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
activeObjectLeft,
|
||||
objectTop - objectHeight / 2 + activeObjectHeight / 2
|
||||
),
|
||||
new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 + activeObjectHeight / 2),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
}
|
||||
|
||||
// snap by the bottom edge
|
||||
if (
|
||||
isInRange(
|
||||
objectTop + objectHeight / 2,
|
||||
activeObjectTop + activeObjectHeight / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
|
||||
horizontalInTheRange = true;
|
||||
horizontalLines.push({
|
||||
y: objectTop + objectHeight / 2,
|
||||
@@ -368,10 +296,7 @@ function initAligningGuidelines(canvas) {
|
||||
: activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset,
|
||||
});
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
activeObjectLeft,
|
||||
objectTop + objectHeight / 2 - activeObjectHeight / 2
|
||||
),
|
||||
new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 - activeObjectHeight / 2),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
@@ -397,7 +322,7 @@ function initAligningGuidelines(canvas) {
|
||||
for (var i = verticalLines.length; i--; ) {
|
||||
drawVerticalLine(verticalLines[i]);
|
||||
}
|
||||
for (var i = horizontalLines.length; i--; ) {
|
||||
for (let i = horizontalLines.length; i--; ) {
|
||||
drawHorizontalLine(horizontalLines[i]);
|
||||
}
|
||||
|
||||
@@ -433,16 +358,14 @@ export function initCenteringGuidelines(canvas) {
|
||||
viewportTransform;
|
||||
|
||||
for (
|
||||
var i = canvasWidthCenter - centerLineMargin,
|
||||
len = canvasWidthCenter + centerLineMargin;
|
||||
var i = canvasWidthCenter - centerLineMargin, len = canvasWidthCenter + centerLineMargin;
|
||||
i <= len;
|
||||
i++
|
||||
) {
|
||||
canvasWidthCenterMap[Math.round(i)] = true;
|
||||
}
|
||||
for (
|
||||
var i = canvasHeightCenter - centerLineMargin,
|
||||
len = canvasHeightCenter + centerLineMargin;
|
||||
let i = canvasHeightCenter - centerLineMargin, len = canvasHeightCenter + centerLineMargin;
|
||||
i <= len;
|
||||
i++
|
||||
) {
|
||||
@@ -450,21 +373,11 @@ export function initCenteringGuidelines(canvas) {
|
||||
}
|
||||
|
||||
function showVerticalCenterLine() {
|
||||
showCenterLine(
|
||||
canvasWidthCenter + 0.5,
|
||||
0,
|
||||
canvasWidthCenter + 0.5,
|
||||
canvasHeight
|
||||
);
|
||||
showCenterLine(canvasWidthCenter + 0.5, 0, canvasWidthCenter + 0.5, canvasHeight);
|
||||
}
|
||||
|
||||
function showHorizontalCenterLine() {
|
||||
showCenterLine(
|
||||
0,
|
||||
canvasHeightCenter + 0.5,
|
||||
canvasWidth,
|
||||
canvasHeightCenter + 0.5
|
||||
);
|
||||
showCenterLine(0, canvasHeightCenter + 0.5, canvasWidth, canvasHeightCenter + 0.5);
|
||||
}
|
||||
|
||||
function showCenterLine(x1, y1, x2, y2) {
|
||||
@@ -493,9 +406,8 @@ export function initCenteringGuidelines(canvas) {
|
||||
|
||||
if (!transform) return;
|
||||
|
||||
(isInVerticalCenter = Math.round(objectCenter.x) in canvasWidthCenterMap),
|
||||
(isInHorizontalCenter =
|
||||
Math.round(objectCenter.y) in canvasHeightCenterMap);
|
||||
((isInVerticalCenter = Math.round(objectCenter.x) in canvasWidthCenterMap),
|
||||
(isInHorizontalCenter = Math.round(objectCenter.y) in canvasHeightCenterMap));
|
||||
|
||||
if (isInHorizontalCenter || isInVerticalCenter) {
|
||||
object.setPositionByOrigin(
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { LayerType, OperationType, createBitmapLayer } from "./layerHelper";
|
||||
// 导入新的复合命令
|
||||
import { CreateImageLayerCommand } from "../commands/LayerCommands";
|
||||
// 导入新的命令
|
||||
import {
|
||||
ChangeFixedImageCommand,
|
||||
AddImageToLayerCommand,
|
||||
} from "../commands/LayerCommands";
|
||||
import { ChangeFixedImageCommand, AddImageToLayerCommand } from "../commands/LayerCommands";
|
||||
import { generateId } from "./helper";
|
||||
import { isBoolean } from "lodash-es";
|
||||
|
||||
/**
|
||||
* 加载并处理图片
|
||||
@@ -101,9 +100,7 @@ export async function createImageLayer({
|
||||
if (isBoolean(undoable)) createImageLayerCmd.undoable = undoable; // 是否撤销
|
||||
|
||||
// 执行复合命令
|
||||
const newLayerId = await layerManager.commandManager.execute(
|
||||
createImageLayerCmd
|
||||
);
|
||||
const newLayerId = await layerManager.commandManager.execute(createImageLayerCmd);
|
||||
|
||||
return newLayerId;
|
||||
} catch (error) {
|
||||
@@ -120,11 +117,7 @@ export async function createImageLayer({
|
||||
* @param {Object} options.fabricImage - 新的图像对象
|
||||
* @returns {Promise<boolean>} 是否成功更改
|
||||
*/
|
||||
export async function changeFixedImage({
|
||||
layerManager,
|
||||
fixedLayerId,
|
||||
fabricImage,
|
||||
} = {}) {
|
||||
export async function changeFixedImage({ layerManager, fixedLayerId, fabricImage } = {}) {
|
||||
if (!layerManager || !fixedLayerId || !fabricImage) {
|
||||
console.error("更改固定图层图像:参数无效");
|
||||
return false;
|
||||
@@ -141,9 +134,7 @@ export async function changeFixedImage({
|
||||
});
|
||||
|
||||
// 通过命令管理器执行
|
||||
const result = await layerManager.commandManager.execute(
|
||||
changeFixedImageCmd
|
||||
);
|
||||
const result = await layerManager.commandManager.execute(changeFixedImageCmd);
|
||||
|
||||
if (result) {
|
||||
console.log(`✅ 成功更改固定图层 "${fixedLayerId}" 的图像`);
|
||||
@@ -192,9 +183,7 @@ export async function addImageToLayer({
|
||||
});
|
||||
|
||||
// 通过命令管理器执行
|
||||
const resultLayerId = await layerManager.commandManager.execute(
|
||||
addImageToLayerCmd
|
||||
);
|
||||
const resultLayerId = await layerManager.commandManager.execute(addImageToLayerCmd);
|
||||
|
||||
if (resultLayerId) {
|
||||
if (targetLayerId) {
|
||||
@@ -219,10 +208,7 @@ export async function addImageToLayer({
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {Promise<string>} 新图层ID的Promise
|
||||
*/
|
||||
export function loadImageUrlToLayer(
|
||||
{ imageUrl, layerManager, canvas, toolManager },
|
||||
options = {}
|
||||
) {
|
||||
export function loadImageUrlToLayer({ imageUrl, layerManager, canvas, toolManager }, options = {}) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!imageUrl || !layerManager || !canvas) {
|
||||
reject(new Error("参数无效"));
|
||||
@@ -231,9 +217,7 @@ export function loadImageUrlToLayer(
|
||||
|
||||
try {
|
||||
// 查找背景图层以获取尺寸
|
||||
const bgLayer = layerManager.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
// 设置最大宽高为背景图层的尺寸
|
||||
const maxWidth = bgLayer?.canvasWidth || canvas.width;
|
||||
@@ -304,9 +288,7 @@ export function uploadImageAndCreateLayer(
|
||||
reader.onload = async (e) => {
|
||||
try {
|
||||
// 查找背景图层以获取尺寸
|
||||
const bgLayer = layerManager.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
// 设置最大宽高为背景图层的尺寸
|
||||
const maxWidth = bgLayer?.canvasWidth || canvas.width;
|
||||
@@ -361,9 +343,7 @@ export function safeLoadImage(imageSource, options = {}) {
|
||||
.then(resolve)
|
||||
.catch((error) => {
|
||||
if (attempt < retries) {
|
||||
console.warn(
|
||||
`图片加载失败,正在重试 (${attempt + 1}/${retries})...`
|
||||
);
|
||||
console.warn(`图片加载失败,正在重试 (${attempt + 1}/${retries})...`);
|
||||
setTimeout(() => attemptLoad(attempt + 1), 500);
|
||||
} else {
|
||||
reject(error);
|
||||
@@ -426,12 +406,7 @@ export function loadImageAndChangeFixedLayer({
|
||||
* @param {Object} options.imageOptions - 图片加载选项
|
||||
* @returns {Promise<string>} 新图像对象ID的Promise
|
||||
*/
|
||||
export function uploadImageAndChangeFixedLayer({
|
||||
file,
|
||||
layerManager,
|
||||
layerId,
|
||||
imageOptions = {},
|
||||
}) {
|
||||
export function uploadImageAndChangeFixedLayer({ file, layerManager, layerId, imageOptions = {} }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!file || !layerManager || !layerId) {
|
||||
reject(new Error("参数无效:需要文件、图层管理器和图层ID"));
|
||||
@@ -449,9 +424,7 @@ export function uploadImageAndChangeFixedLayer({
|
||||
reader.onload = async (e) => {
|
||||
try {
|
||||
// 查找目标固定图层以获取尺寸信息
|
||||
const targetLayer = layerManager.layers.value.find(
|
||||
(layer) => layer.id === layerId
|
||||
);
|
||||
const targetLayer = layerManager.layers.value.find((layer) => layer.id === layerId);
|
||||
|
||||
if (!targetLayer) {
|
||||
throw new Error(`找不到图层 ID: ${layerId}`);
|
||||
@@ -463,9 +436,7 @@ export function uploadImageAndChangeFixedLayer({
|
||||
}
|
||||
|
||||
// 查找背景图层以获取画布尺寸
|
||||
const bgLayer = layerManager.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
const maxWidth = bgLayer?.canvasWidth || layerManager.canvas.width;
|
||||
const maxHeight = bgLayer?.canvasHeight || layerManager.canvas.height;
|
||||
@@ -490,14 +461,10 @@ export function uploadImageAndChangeFixedLayer({
|
||||
});
|
||||
|
||||
// 通过命令管理器执行
|
||||
const newImageId = await layerManager.commandManager.execute(
|
||||
changeFixedImageCmd
|
||||
);
|
||||
const newImageId = await layerManager.commandManager.execute(changeFixedImageCmd);
|
||||
|
||||
if (newImageId) {
|
||||
console.log(
|
||||
`✅ 成功更改固定图层 "${targetLayer.name}" 的图像,新图像ID: ${newImageId}`
|
||||
);
|
||||
console.log(`✅ 成功更改固定图层 "${targetLayer.name}" 的图像,新图像ID: ${newImageId}`);
|
||||
resolve(newImageId);
|
||||
} else {
|
||||
throw new Error("更改固定图层图像失败");
|
||||
@@ -826,9 +793,7 @@ export class AdvancedImageManager {
|
||||
total: imageUrls.length,
|
||||
successful: results.filter((r) => r.success).length,
|
||||
failed: results.filter((r) => !r.success).length,
|
||||
cacheHitRate:
|
||||
this.performanceMetrics.cacheHits /
|
||||
this.performanceMetrics.imageLoads,
|
||||
cacheHitRate: this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads,
|
||||
averageLoadTime: this.performanceMetrics.averageLoadTime,
|
||||
},
|
||||
};
|
||||
@@ -884,13 +849,10 @@ export class AdvancedImageManager {
|
||||
// 立即执行模式
|
||||
for (const operation of operations) {
|
||||
try {
|
||||
const result = await this.canvasManager.changeFixedImage(
|
||||
operation.imageUrl,
|
||||
{
|
||||
targetLayerType: operation.layerType,
|
||||
...operation.options,
|
||||
}
|
||||
);
|
||||
const result = await this.canvasManager.changeFixedImage(operation.imageUrl, {
|
||||
targetLayerType: operation.layerType,
|
||||
...operation.options,
|
||||
});
|
||||
operationResults.push({ success: true, ...result, operation });
|
||||
} catch (error) {
|
||||
operationResults.push({
|
||||
@@ -1038,10 +1000,7 @@ export class AdvancedImageManager {
|
||||
}
|
||||
|
||||
// 替换模板中的变量
|
||||
const operations = this.replaceTemplateVariables(
|
||||
template.operations,
|
||||
variables
|
||||
);
|
||||
const operations = this.replaceTemplateVariables(template.operations, variables);
|
||||
|
||||
// 执行操作
|
||||
const result = await this.batchAddImagesToLayers(operations);
|
||||
@@ -1141,8 +1100,7 @@ export class AdvancedImageManager {
|
||||
this.performanceMetrics.imageLoads++;
|
||||
this.performanceMetrics.totalLoadTime += loadTime;
|
||||
this.performanceMetrics.averageLoadTime =
|
||||
this.performanceMetrics.totalLoadTime /
|
||||
this.performanceMetrics.imageLoads;
|
||||
this.performanceMetrics.totalLoadTime / this.performanceMetrics.imageLoads;
|
||||
}
|
||||
|
||||
getSummary(results) {
|
||||
@@ -1170,12 +1128,9 @@ export class AdvancedImageManager {
|
||||
// 替换字符串中的变量 {{variable}}
|
||||
Object.keys(newOp).forEach((key) => {
|
||||
if (typeof newOp[key] === "string") {
|
||||
newOp[key] = newOp[key].replace(
|
||||
/\{\{(\w+)\}\}/g,
|
||||
(match, varName) => {
|
||||
return variables[varName] || match;
|
||||
}
|
||||
);
|
||||
newOp[key] = newOp[key].replace(/\{\{(\w+)\}\}/g, (match, varName) => {
|
||||
return variables[varName] || match;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1223,10 +1178,7 @@ export class AdvancedImageManager {
|
||||
generatePerformanceRecommendations() {
|
||||
const recommendations = [];
|
||||
|
||||
if (
|
||||
this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads <
|
||||
0.3
|
||||
) {
|
||||
if (this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads < 0.3) {
|
||||
recommendations.push("考虑增加缓存大小以提高缓存命中率");
|
||||
}
|
||||
|
||||
@@ -1298,12 +1250,7 @@ export const ImageUtils = {
|
||||
* @param {string} targetLayerId - 目标图层ID (可选)
|
||||
* @returns {Promise<Object>} 执行结果
|
||||
*/
|
||||
quickAddImageToLayer: (
|
||||
file,
|
||||
layerManager,
|
||||
toolManager,
|
||||
targetLayerId = null
|
||||
) => {
|
||||
quickAddImageToLayer: (file, layerManager, toolManager, targetLayerId = null) => {
|
||||
return uploadImageAndAddToLayerSimple({
|
||||
file,
|
||||
layerManager,
|
||||
@@ -1376,12 +1323,7 @@ export function rasterizeCanvasObjects(options = {}) {
|
||||
function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const {
|
||||
trimWhitespace = true,
|
||||
trimPadding = 10,
|
||||
quality = 1,
|
||||
format = "png",
|
||||
} = options;
|
||||
const { trimWhitespace = true, trimPadding = 10, quality = 1, format = "png" } = options;
|
||||
|
||||
// 保存原始状态
|
||||
const originalObjects = canvas.getObjects();
|
||||
@@ -1389,9 +1331,7 @@ function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
|
||||
const originalZoom = canvas.getZoom();
|
||||
|
||||
// 临时隐藏其他对象,只显示要栅格化的对象
|
||||
const objectsToHide = originalObjects.filter(
|
||||
(obj) => !objects.includes(obj)
|
||||
);
|
||||
const objectsToHide = originalObjects.filter((obj) => !objects.includes(obj));
|
||||
|
||||
// 隐藏不需要的对象
|
||||
objectsToHide.forEach((obj) => {
|
||||
@@ -1415,9 +1355,7 @@ function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
|
||||
const pixelRatio = canvas.getRetinaScaling();
|
||||
|
||||
// 复制画布元素(这会保持所有变换状态)
|
||||
const copiedCanvas = fabric.util.copyCanvasElement(
|
||||
canvas.lowerCanvasEl
|
||||
);
|
||||
const copiedCanvas = fabric.util.copyCanvasElement(canvas.lowerCanvasEl);
|
||||
|
||||
let finalCanvas = copiedCanvas;
|
||||
let trimOffset = { x: 0, y: 0 };
|
||||
@@ -1496,7 +1434,7 @@ function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
|
||||
*/
|
||||
function _restoreObjectVisibility(objects) {
|
||||
objects.forEach((obj) => {
|
||||
if (obj.hasOwnProperty("_originalVisible")) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, "_originalVisible")) {
|
||||
obj.set("visible", obj._originalVisible);
|
||||
delete obj._originalVisible;
|
||||
}
|
||||
@@ -1521,9 +1459,7 @@ function _rasterizeUsingDataURL(canvas, objects, options = {}) {
|
||||
const originalObjects = canvas.getObjects();
|
||||
|
||||
// 临时移除其他对象
|
||||
const objectsToRemove = originalObjects.filter(
|
||||
(obj) => !objects.includes(obj)
|
||||
);
|
||||
const objectsToRemove = originalObjects.filter((obj) => !objects.includes(obj));
|
||||
objectsToRemove.forEach((obj) => {
|
||||
canvas.remove(obj);
|
||||
});
|
||||
@@ -1770,12 +1706,7 @@ function _trimCanvas(canvas, padding = 0) {
|
||||
trimmedCanvas.height = trimHeight;
|
||||
|
||||
// 复制裁剪区域(包含padding)
|
||||
const trimmedImageData = ctx.getImageData(
|
||||
paddedMinX,
|
||||
paddedMinY,
|
||||
trimWidth,
|
||||
trimHeight
|
||||
);
|
||||
const trimmedImageData = ctx.getImageData(paddedMinX, paddedMinY, trimWidth, trimHeight);
|
||||
trimmedCtx.putImageData(trimmedImageData, 0, 0);
|
||||
|
||||
return {
|
||||
@@ -1955,8 +1886,7 @@ export function smartRasterize(options = {}) {
|
||||
function _analyzeRasterizationContext(canvas, objects) {
|
||||
const zoom = canvas.getZoom();
|
||||
const viewportTransform = canvas.viewportTransform;
|
||||
const hasTransform =
|
||||
zoom !== 1 || viewportTransform[4] !== 0 || viewportTransform[5] !== 0;
|
||||
const hasTransform = zoom !== 1 || viewportTransform[4] !== 0 || viewportTransform[5] !== 0;
|
||||
|
||||
// 分析对象类型分布
|
||||
const objectTypes = objects.reduce((acc, obj) => {
|
||||
@@ -1971,9 +1901,7 @@ function _analyzeRasterizationContext(canvas, objects) {
|
||||
// 计算画布利用率
|
||||
const canvasArea = canvas.width * canvas.height;
|
||||
const objectsBounds = _calculateObjectsBounds(objects);
|
||||
const objectsArea = objectsBounds
|
||||
? objectsBounds.width * objectsBounds.height
|
||||
: 0;
|
||||
const objectsArea = objectsBounds ? objectsBounds.width * objectsBounds.height : 0;
|
||||
const utilization = objectsArea / canvasArea;
|
||||
|
||||
return {
|
||||
@@ -1985,9 +1913,7 @@ function _analyzeRasterizationContext(canvas, objects) {
|
||||
utilization,
|
||||
canvasSize: { width: canvas.width, height: canvas.height },
|
||||
objectsBounds,
|
||||
supportsCanvasCopy: !!(
|
||||
fabric.util.copyCanvasElement && canvas.lowerCanvasEl
|
||||
),
|
||||
supportsCanvasCopy: !!(fabric.util.copyCanvasElement && canvas.lowerCanvasEl),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2223,12 +2149,7 @@ function _getAlternativeMethods(analysis) {
|
||||
* @param {number} params.canvasWidth - 画布宽度
|
||||
* @param {number} params.canvasHeight - 画布高度
|
||||
*/
|
||||
export const imageModeHandler = ({
|
||||
imageMode,
|
||||
newImage,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
}) => {
|
||||
export const imageModeHandler = ({ imageMode, newImage, canvasWidth, canvasHeight }) => {
|
||||
switch (imageMode) {
|
||||
case "stretch":
|
||||
// 拉伸模式 - 填充整个画布
|
||||
@@ -2253,10 +2174,8 @@ export const imageModeHandler = ({
|
||||
// 这里可以添加裁剪逻辑,如果需要的话
|
||||
// 例如使用fabric.Image.clipPath来裁剪图像
|
||||
break;
|
||||
case "contains":
|
||||
// 包含模式 - 保证图像在画布内完整显示
|
||||
// 既要考虑画布的宽高比,也要考虑图像的宽高比
|
||||
// 图片缩放后要保证最长边能完全显示在画布内
|
||||
case "contains": {
|
||||
// 图片缩放后要保证最长边能完全显示在画布内 // 既要考虑画布的宽高比,也要考虑图像的宽高比 // 包含模式 - 保证图像在画布内完整显示
|
||||
const canvasAspect = canvasWidth / canvasHeight;
|
||||
const imageAspect = newImage.width / newImage.height;
|
||||
// 保证图像在画布内完整显示 - 既要考虑画布的宽高比,也要考虑图像的宽高比
|
||||
@@ -2269,5 +2188,6 @@ export const imageModeHandler = ({
|
||||
newImage.scaleToHeight(canvasHeight);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -95,8 +95,7 @@ export const BlendMode = {
|
||||
export function isGroupLayer(layer) {
|
||||
if (!layer) return false;
|
||||
return (
|
||||
layer.type === LayerType.GROUP ||
|
||||
(Array.isArray(layer.children) && layer.children.length > 0)
|
||||
layer.type === LayerType.GROUP || (Array.isArray(layer.children) && layer.children.length > 0)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,11 +106,7 @@ export function isGroupLayer(layer) {
|
||||
* @param {Object} options 其他选项
|
||||
* @returns {Object} 创建的图层对象
|
||||
*/
|
||||
export function createLayerFromFabricObject(
|
||||
fabricObject,
|
||||
layerType = "bitmap",
|
||||
options = {}
|
||||
) {
|
||||
export function createLayerFromFabricObject(fabricObject, layerType = "bitmap", options = {}) {
|
||||
if (!fabricObject) return null;
|
||||
|
||||
// 确定图层类型
|
||||
@@ -137,9 +132,7 @@ export function createLayerFromFabricObject(
|
||||
type: type,
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -166,9 +159,7 @@ export function createLayerFromFabricObject(
|
||||
*/
|
||||
export function createLayer(options = {}) {
|
||||
const id =
|
||||
options.id ||
|
||||
generateId("layer_") ||
|
||||
`layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
options.id || generateId("layer_") || `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
return {
|
||||
id: id,
|
||||
// 图层基本属性
|
||||
@@ -208,8 +199,7 @@ export function createLayer(options = {}) {
|
||||
* @returns {Object} 背景图层对象
|
||||
*/
|
||||
export function createBackgroundLayer(options = {}) {
|
||||
const id =
|
||||
options.id || `bg_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
const id = options.id || `bg_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
return {
|
||||
id: id,
|
||||
// 图层基本属性
|
||||
@@ -400,9 +390,7 @@ export function createSmartObjectLayer(options = {}) {
|
||||
* @returns {Object} 固定图层对象
|
||||
*/
|
||||
export function createFixedLayer(options = {}) {
|
||||
const id =
|
||||
options.id ||
|
||||
`fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
const id = options.id || `fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
return {
|
||||
id: id,
|
||||
// 图层基本属性
|
||||
@@ -445,9 +433,7 @@ export function cloneLayer(layer) {
|
||||
// 复制 fabric 对象 (如果存在)
|
||||
if (Array.isArray(layer.fabricObjects)) {
|
||||
clonedLayer.fabricObjects = layer.fabricObjects.map((obj) => {
|
||||
return obj && typeof obj.clone === "function"
|
||||
? obj.clone()
|
||||
: JSON.parse(JSON.stringify(obj));
|
||||
return obj && typeof obj.clone === "function" ? obj.clone() : JSON.parse(JSON.stringify(obj));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -15,18 +15,13 @@ export function buildLayerAssociations(layer, canvasObjects) {
|
||||
// 处理单个fabricObject关联
|
||||
if (layer.fabricObject) {
|
||||
// 如果图层已经有关联的fabricObject,确保它的layerId和layerName正确
|
||||
layer.fabricObject =
|
||||
canvasObjects.find((obj) => obj.id === layer.fabricObject.id) || null;
|
||||
layer.fabricObject = canvasObjects.find((obj) => obj.id === layer.fabricObject.id) || null;
|
||||
}
|
||||
|
||||
if (layer.clippingMask) {
|
||||
// clippingMask 可能是一个fabricObject或组 也可能是一个简单对象
|
||||
const clippingMaskObj = canvasObjects.find(
|
||||
(obj) => obj.id === layer.clippingMask.id
|
||||
);
|
||||
layer.clippingMask = clippingMaskObj
|
||||
? clippingMaskObj?.toObject?.(["id"])
|
||||
: layer.clippingMask;
|
||||
const clippingMaskObj = canvasObjects.find((obj) => obj.id === layer.clippingMask.id);
|
||||
layer.clippingMask = clippingMaskObj ? clippingMaskObj?.toObject?.(["id"]) : layer.clippingMask;
|
||||
}
|
||||
|
||||
// 处理多个fabricObjects关联
|
||||
@@ -172,10 +167,7 @@ export function simplifyLayers(layers) {
|
||||
opacity: layer.opacity,
|
||||
isBackground: layer.isBackground || false,
|
||||
isFixed: layer.isFixed || false,
|
||||
clippingMask:
|
||||
layer.clippingMask?.toObject?.(["id", "layerId"]) ||
|
||||
layer.clippingMask ||
|
||||
null, // 可能是一个fabricObject或组 也可能是一个简单对象
|
||||
clippingMask: layer.clippingMask?.toObject?.(["id", "layerId"]) || layer.clippingMask || null, // 可能是一个fabricObject或组 也可能是一个简单对象
|
||||
// ? {
|
||||
// id: layer.clippingMask.id,
|
||||
// type: layer.clippingMask.type,
|
||||
@@ -200,10 +192,7 @@ export function simplifyLayers(layers) {
|
||||
)
|
||||
.filter((obj) => obj !== null)
|
||||
: [],
|
||||
children:
|
||||
layer.children && isArray(layer.children)
|
||||
? simplifyLayers(layer.children)
|
||||
: [],
|
||||
children: layer.children && isArray(layer.children) ? simplifyLayers(layer.children) : [],
|
||||
};
|
||||
|
||||
return simplifiedLayer;
|
||||
@@ -240,9 +229,7 @@ export function restoreLayers(simplifiedLayers, canvasObjects) {
|
||||
|
||||
if (layer.clippingMask) {
|
||||
// clippingMask 可能是一个fabricObject或组 也可能是一个简单对象
|
||||
const clippingMaskObj = canvasObjects.find(
|
||||
(obj) => obj.id === layer.clippingMask.id
|
||||
);
|
||||
const clippingMaskObj = canvasObjects.find((obj) => obj.id === layer.clippingMask.id);
|
||||
restoredLayer.clippingMask = clippingMaskObj
|
||||
? clippingMaskObj?.toObject?.(["id"])
|
||||
: layer.clippingMask;
|
||||
@@ -250,17 +237,11 @@ export function restoreLayers(simplifiedLayers, canvasObjects) {
|
||||
|
||||
// 恢复单个fabricObject关联
|
||||
if (layer.fabricObject?.id) {
|
||||
const fabricObj = canvasObjects.find(
|
||||
(obj) => obj.id === layer.fabricObject.id
|
||||
);
|
||||
const fabricObj = canvasObjects.find((obj) => obj.id === layer.fabricObject.id);
|
||||
if (fabricObj) {
|
||||
fabricObj.layerId = layer.id;
|
||||
fabricObj.layerName = layer.name;
|
||||
restoredLayer.fabricObject = fabricObj.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"type",
|
||||
]);
|
||||
restoredLayer.fabricObject = fabricObj.toObject(["id", "layerId", "type"]);
|
||||
} else {
|
||||
restoredLayer.fabricObject = null;
|
||||
}
|
||||
@@ -270,9 +251,7 @@ export function restoreLayers(simplifiedLayers, canvasObjects) {
|
||||
if (layer.fabricObjects && isArray(layer.fabricObjects)) {
|
||||
restoredLayer.fabricObjects = layer.fabricObjects
|
||||
.map((fabricRef) => {
|
||||
const fabricObj = canvasObjects.find(
|
||||
(obj) => obj.id === fabricRef.id
|
||||
);
|
||||
const fabricObj = canvasObjects.find((obj) => obj.id === fabricRef.id);
|
||||
if (fabricObj) {
|
||||
fabricObj.layerId = layer.id;
|
||||
fabricObj.layerName = layer.name;
|
||||
|
||||
@@ -18,10 +18,8 @@ export async function restoreFabricObject(serializedObject, canvas) {
|
||||
|
||||
// 恢复自定义属性
|
||||
if (serializedObject.id) fabricObject.id = serializedObject.id;
|
||||
if (serializedObject.layerId)
|
||||
fabricObject.layerId = serializedObject.layerId;
|
||||
if (serializedObject.layerName)
|
||||
fabricObject.layerName = serializedObject.layerName;
|
||||
if (serializedObject.layerId) fabricObject.layerId = serializedObject.layerId;
|
||||
if (serializedObject.layerName) fabricObject.layerName = serializedObject.layerName;
|
||||
|
||||
// 更新坐标
|
||||
fabricObject.setCoords();
|
||||
|
||||
@@ -22,11 +22,7 @@ export const createRasterizedImage = async ({
|
||||
isGroupWithMask = false, // 是否为带遮罩的组图层
|
||||
} = {}) => {
|
||||
try {
|
||||
console.log(
|
||||
`📊 开始栅格化 ${fabricObjects.length} 个对象${
|
||||
maskObject ? "(带遮罩)" : ""
|
||||
}`
|
||||
);
|
||||
console.log(`📊 开始栅格化 ${fabricObjects.length} 个对象${maskObject ? "(带遮罩)" : ""}`);
|
||||
|
||||
// 确保有对象需要栅格化
|
||||
if (fabricObjects.length === 0) {
|
||||
@@ -36,10 +32,7 @@ export const createRasterizedImage = async ({
|
||||
|
||||
// 高清倍数
|
||||
const currentZoom = canvas.getZoom?.() || 1;
|
||||
scaleFactor = Math.max(
|
||||
scaleFactor || canvas?.getRetinaScaling?.(),
|
||||
currentZoom
|
||||
);
|
||||
scaleFactor = Math.max(scaleFactor || canvas?.getRetinaScaling?.(), currentZoom);
|
||||
|
||||
if (isThumbnail) scaleFactor = 0.2; // 缩略图使用较小的高清倍数
|
||||
|
||||
@@ -191,9 +184,7 @@ const createRasterizedImageWithGroup = async ({
|
||||
hasControls: false,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
|
||||
);
|
||||
console.log(`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`);
|
||||
|
||||
// 调整组的位置,让它位于画布的左上角
|
||||
group.set({
|
||||
@@ -317,9 +308,7 @@ const createRasterizedImageWithMask = async ({
|
||||
height: canvasHeight,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
|
||||
);
|
||||
console.log(`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`);
|
||||
|
||||
// 调整对象位置,相对于遮罩边界重新定位
|
||||
clonedObjects.forEach((obj) => {
|
||||
|
||||
@@ -85,18 +85,12 @@ const createClippedObjects = async ({
|
||||
console.log("🎯 使用新的图像遮罩裁剪方法创建对象");
|
||||
|
||||
// 使用优化后的边界计算,确保包含描边区域
|
||||
const optimizedBounds = calculateOptimizedBounds(
|
||||
clippingObject,
|
||||
fabricObjects
|
||||
);
|
||||
const optimizedBounds = calculateOptimizedBounds(clippingObject, fabricObjects);
|
||||
console.log("📐 优化后的选区边界框:", optimizedBounds);
|
||||
|
||||
// 获取羽化值
|
||||
let featherAmount = 0;
|
||||
if (
|
||||
selectionManager &&
|
||||
typeof selectionManager.getFeatherAmount === "function"
|
||||
) {
|
||||
if (selectionManager && typeof selectionManager.getFeatherAmount === "function") {
|
||||
featherAmount = selectionManager.getFeatherAmount();
|
||||
console.log(`🌟 应用羽化效果: ${featherAmount}px`);
|
||||
}
|
||||
@@ -177,10 +171,7 @@ const createClippedDataURLByCanvas = async ({
|
||||
console.log("🖼️ 使用图像遮罩裁剪方法生成DataURL");
|
||||
|
||||
// 使用优化后的边界计算,确保包含描边区域
|
||||
const optimizedBounds = calculateOptimizedBounds(
|
||||
clippingObject,
|
||||
fabricObjects
|
||||
);
|
||||
const optimizedBounds = calculateOptimizedBounds(clippingObject, fabricObjects);
|
||||
|
||||
// 使用高分辨率以保证质量
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
@@ -239,13 +230,7 @@ const createClippedDataURLByCanvas = async ({
|
||||
* 创建简单克隆对象
|
||||
* 当不需要裁剪时,直接克隆原对象
|
||||
*/
|
||||
const createSimpleClone = async ({
|
||||
canvas,
|
||||
fabricObjects,
|
||||
isReturenDataURL,
|
||||
quality,
|
||||
format,
|
||||
}) => {
|
||||
const createSimpleClone = async ({ canvas, fabricObjects, isReturenDataURL, quality, format }) => {
|
||||
try {
|
||||
console.log("📋 创建简单克隆对象");
|
||||
|
||||
@@ -459,10 +444,7 @@ const createLegacyRasterization = async ({
|
||||
|
||||
// 这里保留原有的离屏渲染逻辑作为备选方案
|
||||
const currentZoom = canvas.getZoom?.() || 1;
|
||||
scaleFactor = Math.max(
|
||||
scaleFactor || canvas?.getRetinaScaling?.(),
|
||||
currentZoom
|
||||
);
|
||||
scaleFactor = Math.max(scaleFactor || canvas?.getRetinaScaling?.(), currentZoom);
|
||||
scaleFactor = Math.min(scaleFactor, 3);
|
||||
|
||||
const { absoluteBounds, relativeBounds } = calculateBounds(fabricObjects);
|
||||
@@ -592,9 +574,7 @@ const createOffscreenRasterization = async ({
|
||||
height: canvasHeight,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
|
||||
);
|
||||
console.log(`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`);
|
||||
|
||||
// 克隆对象到离屏画布
|
||||
const clonedObjects = [];
|
||||
@@ -749,11 +729,7 @@ export const getObjectsBounds = (fabricObjects) => {
|
||||
* @param {Number} qualityMultiplier 质量倍数
|
||||
* @returns {Promise<String>} 遮罩图像的DataURL
|
||||
*/
|
||||
const createMaskImageFromPath = async ({
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier,
|
||||
}) => {
|
||||
const createMaskImageFromPath = async ({ clippingObject, selectionBounds, qualityMultiplier }) => {
|
||||
try {
|
||||
console.log("🎭 创建路径遮罩图像");
|
||||
|
||||
@@ -769,11 +745,7 @@ const createMaskImageFromPath = async ({
|
||||
});
|
||||
|
||||
// 克隆路径对象并处理描边转填充
|
||||
const maskPath = await createSolidMaskPath(
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier
|
||||
);
|
||||
const maskPath = await createSolidMaskPath(clippingObject, selectionBounds, qualityMultiplier);
|
||||
|
||||
// 添加路径到遮罩画布
|
||||
maskCanvas.add(maskPath);
|
||||
@@ -804,11 +776,7 @@ const createMaskImageFromPath = async ({
|
||||
* @param {Number} qualityMultiplier 质量倍数
|
||||
* @returns {Promise<String>} 内容图像的DataURL
|
||||
*/
|
||||
const renderContentToImage = async ({
|
||||
fabricObjects,
|
||||
selectionBounds,
|
||||
qualityMultiplier,
|
||||
}) => {
|
||||
const renderContentToImage = async ({ fabricObjects, selectionBounds, qualityMultiplier }) => {
|
||||
try {
|
||||
console.log("🖼️ 渲染内容图像");
|
||||
|
||||
@@ -964,11 +932,7 @@ const createAdvancedMaskImage = async ({
|
||||
});
|
||||
|
||||
// 克隆路径对象并处理描边转填充
|
||||
const maskPath = await createSolidMaskPath(
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier
|
||||
);
|
||||
const maskPath = await createSolidMaskPath(clippingObject, selectionBounds, qualityMultiplier);
|
||||
|
||||
// 如果有羽化值,添加模糊效果
|
||||
if (featherAmount > 0) {
|
||||
@@ -987,10 +951,7 @@ const createAdvancedMaskImage = async ({
|
||||
|
||||
// 如果有羽化,需要进行后处理
|
||||
if (featherAmount > 0) {
|
||||
return await applyCanvasBlur(
|
||||
maskCanvas,
|
||||
featherAmount * qualityMultiplier
|
||||
);
|
||||
return await applyCanvasBlur(maskCanvas, featherAmount * qualityMultiplier);
|
||||
}
|
||||
|
||||
// 生成遮罩图像
|
||||
@@ -1066,11 +1027,7 @@ const applyCanvasBlur = async (canvas, blurAmount) => {
|
||||
* @param {Number} qualityMultiplier 质量倍数
|
||||
* @returns {Promise<fabric.Object>} 处理后的遮罩路径对象
|
||||
*/
|
||||
const createSolidMaskPath = async (
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier
|
||||
) => {
|
||||
const createSolidMaskPath = async (clippingObject, selectionBounds, qualityMultiplier) => {
|
||||
try {
|
||||
console.log("🔧 创建实体遮罩路径,处理描边转填充");
|
||||
|
||||
@@ -1081,29 +1038,19 @@ const createSolidMaskPath = async (
|
||||
const hasStroke = maskPath.stroke && maskPath.strokeWidth > 0;
|
||||
|
||||
if (hasStroke) {
|
||||
console.log(
|
||||
`📏 检测到描边: ${maskPath.stroke}, 宽度: ${maskPath.strokeWidth}`
|
||||
);
|
||||
console.log(`📏 检测到描边: ${maskPath.stroke}, 宽度: ${maskPath.strokeWidth}`);
|
||||
|
||||
// 对于有描边的路径,我们需要更精确的处理
|
||||
const strokeWidth = maskPath.strokeWidth;
|
||||
|
||||
// 方法1: 如果是简单的几何形状(矩形、圆形等),可以通过调整尺寸来补偿描边
|
||||
if (
|
||||
maskPath.type === "rect" ||
|
||||
maskPath.type === "circle" ||
|
||||
maskPath.type === "ellipse"
|
||||
) {
|
||||
if (maskPath.type === "rect" || maskPath.type === "circle" || maskPath.type === "ellipse") {
|
||||
// 对于矩形和椭圆,增加宽高来包含描边
|
||||
const strokeOffset = strokeWidth;
|
||||
|
||||
maskPath.set({
|
||||
left:
|
||||
(maskPath.left - selectionBounds.left - strokeOffset / 2) *
|
||||
qualityMultiplier,
|
||||
top:
|
||||
(maskPath.top - selectionBounds.top - strokeOffset / 2) *
|
||||
qualityMultiplier,
|
||||
left: (maskPath.left - selectionBounds.left - strokeOffset / 2) * qualityMultiplier,
|
||||
top: (maskPath.top - selectionBounds.top - strokeOffset / 2) * qualityMultiplier,
|
||||
scaleX: (maskPath.scaleX || 1) * qualityMultiplier,
|
||||
scaleY: (maskPath.scaleY || 1) * qualityMultiplier,
|
||||
width: (maskPath.width || 0) + strokeOffset,
|
||||
@@ -1122,12 +1069,8 @@ const createSolidMaskPath = async (
|
||||
const strokeOffset = strokeWidth / 2;
|
||||
|
||||
maskPath.set({
|
||||
left:
|
||||
(maskPath.left - selectionBounds.left - strokeOffset) *
|
||||
qualityMultiplier,
|
||||
top:
|
||||
(maskPath.top - selectionBounds.top - strokeOffset) *
|
||||
qualityMultiplier,
|
||||
left: (maskPath.left - selectionBounds.left - strokeOffset) * qualityMultiplier,
|
||||
top: (maskPath.top - selectionBounds.top - strokeOffset) * qualityMultiplier,
|
||||
scaleX: (maskPath.scaleX || 1) * qualityMultiplier * expandRatio,
|
||||
scaleY: (maskPath.scaleY || 1) * qualityMultiplier * expandRatio,
|
||||
fill: "#ffffff",
|
||||
|
||||
Reference in New Issue
Block a user