2025-06-18 11:05:23 +08:00
|
|
|
|
import { fabric } from "fabric-with-all";
|
2025-07-21 01:17:25 +08:00
|
|
|
|
import initAligningGuidelines, {
|
|
|
|
|
|
initCenteringGuidelines,
|
|
|
|
|
|
} from "../utils/helperLine";
|
2025-06-09 10:25:54 +08:00
|
|
|
|
import { ThumbnailManager } from "./ThumbnailManager";
|
|
|
|
|
|
import { ExportManager } from "./ExportManager";
|
|
|
|
|
|
import {
|
2025-06-26 00:37:07 +08:00
|
|
|
|
findLayerRecursively,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
isGroupLayer,
|
|
|
|
|
|
OperationType,
|
|
|
|
|
|
OperationTypes,
|
|
|
|
|
|
} from "../utils/layerHelper";
|
|
|
|
|
|
import { AnimationManager } from "./animation/AnimationManager";
|
|
|
|
|
|
import { createCanvas } from "../utils/canvasFactory";
|
|
|
|
|
|
import { CanvasEventManager } from "./events/CanvasEventManager";
|
|
|
|
|
|
import CanvasConfig from "../config/canvasConfig";
|
|
|
|
|
|
import { RedGreenModeManager } from "./RedGreenModeManager";
|
2025-06-18 11:05:23 +08:00
|
|
|
|
import { EraserStateManager } from "./EraserStateManager";
|
2025-07-21 01:17:25 +08:00
|
|
|
|
import {
|
|
|
|
|
|
deepClone,
|
|
|
|
|
|
findObjectById,
|
|
|
|
|
|
generateId,
|
|
|
|
|
|
optimizeCanvasRendering,
|
|
|
|
|
|
} from "../utils/helper";
|
2025-06-18 11:05:23 +08:00
|
|
|
|
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
|
|
|
|
|
|
import { isFunction } from "lodash-es";
|
2025-06-09 10:25:54 +08:00
|
|
|
|
import {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
restoreObjectLayerAssociations,
|
|
|
|
|
|
simplifyLayers,
|
|
|
|
|
|
validateLayerAssociations,
|
|
|
|
|
|
} from "../utils/layerUtils";
|
2025-06-29 23:29:47 +08:00
|
|
|
|
import { imageModeHandler } from "../utils/imageHelper";
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
export class CanvasManager {
|
|
|
|
|
|
constructor(canvasElement, options) {
|
|
|
|
|
|
this.canvasElement = canvasElement;
|
|
|
|
|
|
this.width = options.width || 1024;
|
|
|
|
|
|
this.height = options.height || 768;
|
|
|
|
|
|
this.backgroundColor = options.backgroundColor || "#ffffff";
|
|
|
|
|
|
this.toolManager = options.toolManager || null; // 工具管理器引用
|
|
|
|
|
|
this.currentZoom = options.currentZoom || { value: 100 };
|
|
|
|
|
|
this.maskLayer = null; // 添加蒙层引用
|
|
|
|
|
|
this.editorMode = CanvasConfig.defaultTool; // 默认编辑器模式
|
|
|
|
|
|
this.layers = options.layers || null; // 图层引用
|
2025-07-16 18:08:20 +08:00
|
|
|
|
this.lastSelectLayerId = options.lastSelectLayerId || null; // 最后选择的图层ID
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.canvasWidth = options.canvasWidth || this.width; // 画布宽度
|
|
|
|
|
|
this.canvasHeight = options.canvasHeight || this.height; // 画布高度
|
|
|
|
|
|
this.canvasColor = options.canvasColor || "#ffffff"; // 画布背景颜色
|
|
|
|
|
|
this.enabledRedGreenMode = options.enabledRedGreenMode || false; // 是否启用红绿图模式
|
2025-06-26 00:37:07 +08:00
|
|
|
|
this.isFixedErasable = options.isFixedErasable || false; // 是否允许擦除固定图层
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.eraserStateManager = null; // 橡皮擦状态管理器引用
|
2025-06-26 00:37:07 +08:00
|
|
|
|
this.handleCanvasInit = null; // 画布初始化回调函数
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 初始化画布
|
|
|
|
|
|
this.initializeCanvas();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
initializeCanvas() {
|
|
|
|
|
|
console.log("fabric.version:", fabric.version);
|
|
|
|
|
|
this.canvas = createCanvas(this.canvasElement, {
|
|
|
|
|
|
width: this.width,
|
|
|
|
|
|
height: this.height,
|
|
|
|
|
|
preserveObjectStacking: true,
|
|
|
|
|
|
enableRetinaScaling: true,
|
|
|
|
|
|
stopContextMenu: true,
|
|
|
|
|
|
fireRightClick: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化动画管理器
|
|
|
|
|
|
this.animationManager = new AnimationManager(this.canvas, {
|
|
|
|
|
|
currentZoom: this.currentZoom,
|
|
|
|
|
|
wheelThrottleTime: 15, // 降低滚轮事件节流时间,提高响应性
|
|
|
|
|
|
defaultEase: "power2.lin",
|
|
|
|
|
|
defaultDuration: 0.3, // 缩短默认动画时间
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化缩略图管理器
|
|
|
|
|
|
this.thumbnailManager = new ThumbnailManager(this.canvas, {
|
|
|
|
|
|
// 可以根据需求自定义选项
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// layerThumbSize: { width: 32, height: 32 },
|
|
|
|
|
|
// elementThumbSize: { width: 32, height: 24 },
|
2025-06-09 10:25:54 +08:00
|
|
|
|
layers: this.layers,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.canvas.thumbnailManager = this.thumbnailManager; // 将缩略图管理器绑定到画布
|
|
|
|
|
|
|
2025-06-26 00:37:07 +08:00
|
|
|
|
// // 设置画布辅助线
|
|
|
|
|
|
// initAligningGuidelines(this.canvas);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-26 00:37:07 +08:00
|
|
|
|
// // 设置画布中心线
|
|
|
|
|
|
// initCenteringGuidelines(this.canvas);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化画布事件监听器
|
|
|
|
|
|
this._initCanvasEvents();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-26 00:37:07 +08:00
|
|
|
|
// 添加图像到指定图层
|
|
|
|
|
|
async addImageToLayer({ targetLayerId, fabricImage, ...options }) {
|
|
|
|
|
|
// 如果图层管理器存在,将图像合并到当前活动图层
|
|
|
|
|
|
if (this.layerManager) {
|
|
|
|
|
|
// 获取当前活动图层
|
|
|
|
|
|
let activeLayer = targetLayerId
|
|
|
|
|
|
? findLayerRecursively(this.layers.value, targetLayerId)?.layer
|
|
|
|
|
|
: this.layerManager.getActiveLayer();
|
|
|
|
|
|
|
|
|
|
|
|
if (activeLayer) {
|
|
|
|
|
|
// 确保新图像具有正确的图层信息
|
|
|
|
|
|
fabricImage.set({
|
|
|
|
|
|
layerId: activeLayer.id,
|
|
|
|
|
|
layerName: activeLayer.name,
|
|
|
|
|
|
id: fabricImage.id || generateId("brush_img_"),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-29 23:29:47 +08:00
|
|
|
|
if (options.imageMode) {
|
|
|
|
|
|
imageModeHandler({
|
|
|
|
|
|
imageMode: options.imageMode,
|
|
|
|
|
|
newImage: fabricImage,
|
|
|
|
|
|
canvasWidth: this.canvasWidth.value,
|
|
|
|
|
|
canvasHeight: this.canvasHeight.value,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 默认居中
|
|
|
|
|
|
fabricImage.set({
|
|
|
|
|
|
originX: "center",
|
|
|
|
|
|
originY: "center",
|
|
|
|
|
|
left: this.canvas.width / 2,
|
|
|
|
|
|
top: this.canvas.height / 2,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-26 00:37:07 +08:00
|
|
|
|
// 执行高保真合并操作
|
|
|
|
|
|
await this.eventManager?.mergeLayerObjectsForPerformance?.({
|
|
|
|
|
|
fabricImage,
|
|
|
|
|
|
activeLayer,
|
|
|
|
|
|
options,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.thumbnailManager?.generateLayerThumbnail(activeLayer.id);
|
|
|
|
|
|
|
|
|
|
|
|
// 返回true表示不要自动添加到画布,因为我们已经通过图层管理器处理了
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.warn("没有活动图层,无法添加图像");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 初始化画布事件监听器
|
|
|
|
|
|
* 设置鼠标事件、键盘事件等
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_initCanvasEvents() {
|
|
|
|
|
|
// 添加笔刷图像转换处理回调
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas.onBrushImageConverted = async (fabricImage) => {
|
2025-06-26 00:37:07 +08:00
|
|
|
|
await this.addImageToLayer({ fabricImage, targetLayerId: null });
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 返回false表示使用默认行为(直接添加到画布)
|
|
|
|
|
|
return false;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
};
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
2025-07-21 01:17:25 +08:00
|
|
|
|
this.eraserStateManager = new EraserStateManager(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
this.layerManager
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 监听擦除开始事件
|
|
|
|
|
|
this.canvas.on("erasing:start", () => {
|
|
|
|
|
|
console.log("开始擦除");
|
|
|
|
|
|
this.eraserStateManager.startErasing();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 监听擦除结束事件
|
|
|
|
|
|
this.canvas.on("erasing:end", async (e) => {
|
|
|
|
|
|
console.log("擦除完成", e.targets);
|
|
|
|
|
|
// 可以在这里保存状态到命令管理器
|
|
|
|
|
|
const affectedObjects = e.targets || [];
|
|
|
|
|
|
const command = this.eraserStateManager.endErasing(affectedObjects);
|
|
|
|
|
|
if (command && this.commandManager) {
|
|
|
|
|
|
await this.commandManager?.executeCommand?.(command);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await command?.execute?.(); // 如果没有命令管理器,直接执行命令
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新交互性
|
2025-07-21 01:17:25 +08:00
|
|
|
|
command &&
|
|
|
|
|
|
(await this.layerManager?.updateLayersObjectsInteractivity?.());
|
2025-06-22 13:52:28 +08:00
|
|
|
|
|
2025-07-21 01:17:25 +08:00
|
|
|
|
this.thumbnailManager?.generateLayerThumbnail(
|
|
|
|
|
|
this.layerManager?.activeLayerId?.value
|
|
|
|
|
|
);
|
2025-06-26 00:37:07 +08:00
|
|
|
|
|
|
|
|
|
|
// 固定图层 的擦除也需要重新生成缩略图 要判断 当前固定图层是否锁定
|
2025-07-21 01:17:25 +08:00
|
|
|
|
const fixedLayer = this.layers?.value?.find(
|
|
|
|
|
|
(layer) => layer.isFixed && !layer.locked
|
|
|
|
|
|
);
|
2025-06-26 00:37:07 +08:00
|
|
|
|
// 如果有固定图层且未锁定,则生成缩略图
|
|
|
|
|
|
fixedLayer &&
|
|
|
|
|
|
this.isFixedErasable &&
|
|
|
|
|
|
this.thumbnailManager?.generateLayerThumbnail(fixedLayer?.id);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置编辑器模式
|
|
|
|
|
|
* @param {string} mode 'draw'、'select'或'pan'
|
|
|
|
|
|
*/
|
|
|
|
|
|
toolChanged(mode) {
|
|
|
|
|
|
if (!OperationTypes.includes(mode)) {
|
|
|
|
|
|
console.warn(`不支持的编辑器模式: ${mode}`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.editorMode = mode;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已创建事件管理器,更新它的编辑器模式
|
|
|
|
|
|
if (this.eventManager) {
|
|
|
|
|
|
this.eventManager.setEditorMode(mode);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setToolManager(toolManager) {
|
|
|
|
|
|
this.toolManager = toolManager || null; // 工具管理器引用
|
|
|
|
|
|
|
|
|
|
|
|
// 更新红绿图模式管理器的工具管理器引用
|
|
|
|
|
|
if (this.redGreenModeManager) {
|
|
|
|
|
|
this.redGreenModeManager.toolManager = this.toolManager;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已创建事件管理器,更新它的工具管理器引用
|
|
|
|
|
|
if (this.eventManager) {
|
|
|
|
|
|
this.eventManager.toolManager = this.toolManager;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setLayerManager(layerManager) {
|
|
|
|
|
|
this.layerManager = layerManager || null; // 图层管理器引用
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化导出管理器(需要在图层管理器设置后初始化)
|
|
|
|
|
|
if (this.layerManager) {
|
|
|
|
|
|
this.exportManager = new ExportManager(this, this.layerManager);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新红绿图模式管理器的图层管理器引用
|
|
|
|
|
|
if (this.redGreenModeManager) {
|
|
|
|
|
|
this.redGreenModeManager.layerManager = this.layerManager;
|
|
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
if (this.eraserStateManager) {
|
|
|
|
|
|
this.eraserStateManager.setLayerManager(this.layerManager);
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置命令管理器
|
|
|
|
|
|
* @param {Object} commandManager 命令管理器实例
|
|
|
|
|
|
*/
|
|
|
|
|
|
setCommandManager(commandManager) {
|
|
|
|
|
|
this.commandManager = commandManager;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新红绿图模式管理器的命令管理器引用
|
|
|
|
|
|
if (this.redGreenModeManager) {
|
|
|
|
|
|
this.redGreenModeManager.commandManager = this.commandManager;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置液化管理器
|
|
|
|
|
|
* @param {Object} liquifyManager 液化管理器实例
|
|
|
|
|
|
*/
|
|
|
|
|
|
setLiquifyManager(liquifyManager) {
|
|
|
|
|
|
this.liquifyManager = liquifyManager;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置选区管理器
|
|
|
|
|
|
* @param {Object} selectionManager 选区管理器实例
|
|
|
|
|
|
*/
|
|
|
|
|
|
setSelectionManager(selectionManager) {
|
|
|
|
|
|
this.selectionManager = selectionManager;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已创建事件管理器,更新它的选区管理器引用
|
|
|
|
|
|
if (this.eventManager) {
|
|
|
|
|
|
this.eventManager.selectionManager = this.selectionManager;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置红绿图模式管理器
|
|
|
|
|
|
setRedGreenModeManager(redGreenModeManager) {
|
|
|
|
|
|
this.redGreenModeManager = redGreenModeManager;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setupCanvasEvents(activeElementId, layerManager) {
|
|
|
|
|
|
// 创建画布事件管理器
|
|
|
|
|
|
this.eventManager = new CanvasEventManager(this.canvas, {
|
|
|
|
|
|
toolManager: this.toolManager,
|
|
|
|
|
|
animationManager: this.animationManager,
|
|
|
|
|
|
thumbnailManager: this.thumbnailManager,
|
|
|
|
|
|
editorMode: this.editorMode,
|
|
|
|
|
|
activeElementId: activeElementId,
|
|
|
|
|
|
layerManager: layerManager,
|
|
|
|
|
|
layers: this.layers,
|
2025-07-16 18:08:20 +08:00
|
|
|
|
lastSelectLayerId: this.lastSelectLayerId,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 设置动画交互效果
|
|
|
|
|
|
this.animationManager.setupInteractionAnimations();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-26 00:37:07 +08:00
|
|
|
|
setupCanvasInitEvent(handleCanvasInit) {
|
|
|
|
|
|
this.handleCanvasInit = handleCanvasInit;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
setupLongPress(callback) {
|
|
|
|
|
|
if (this.eventManager) {
|
|
|
|
|
|
this.eventManager.setupLongPress(callback);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updateSelectedElements(opt, activeElementId) {
|
|
|
|
|
|
if (this.eventManager) {
|
|
|
|
|
|
this.eventManager.updateSelectedElements(opt);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const selected = opt.selected[0];
|
|
|
|
|
|
if (selected) {
|
|
|
|
|
|
activeElementId.value = selected.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
clearSelectedElements(activeElementId) {
|
|
|
|
|
|
if (this.eventManager) {
|
|
|
|
|
|
this.eventManager.clearSelectedElements();
|
|
|
|
|
|
} else if (activeElementId) {
|
|
|
|
|
|
activeElementId.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用动画管理器的缩放方法
|
|
|
|
|
|
animateZoom(point, targetZoom, options = {}) {
|
|
|
|
|
|
this.animationManager.animateZoom(point, targetZoom, options);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 应用缩放(为兼容性保留)
|
|
|
|
|
|
_applyZoom(point, zoom, skipUpdate = false) {
|
|
|
|
|
|
this.animationManager._applyZoom(point, zoom, skipUpdate);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用动画管理器的平移方法
|
|
|
|
|
|
animatePan(targetPosition, options = {}) {
|
|
|
|
|
|
this.animationManager.animatePan(targetPosition, options);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 应用平移(为兼容性保留)
|
|
|
|
|
|
_applyPan(x, y) {
|
|
|
|
|
|
this.animationManager._applyPan(x, y);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 平移到指定元素
|
|
|
|
|
|
panToElement(elementId) {
|
|
|
|
|
|
this.animationManager.panToElement(elementId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置缩放并居中内容
|
|
|
|
|
|
async resetZoom(animated = true) {
|
|
|
|
|
|
// 先重置缩放
|
|
|
|
|
|
await this.animationManager.resetZoom(animated);
|
|
|
|
|
|
|
|
|
|
|
|
// // 重置视图变换以确保元素位置正确
|
|
|
|
|
|
// this._resetViewportTransform();
|
|
|
|
|
|
|
|
|
|
|
|
// // 居中所有画布元素,包括背景层和其他元素
|
|
|
|
|
|
// this.centerAllObjects();
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染画布使变更生效
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-24 01:54:37 +08:00
|
|
|
|
// 设置固定图层可擦除状态
|
|
|
|
|
|
setFixedLayerErasable({ type = "isFixed", flag = false }) {
|
|
|
|
|
|
const layer = this.layers.value.find((layer) => layer[type]);
|
|
|
|
|
|
if (layer) {
|
|
|
|
|
|
// 设置固定图层的可擦除状态
|
|
|
|
|
|
layer.locked = flag;
|
|
|
|
|
|
// 更新画布对象的erasable属性
|
2025-07-21 01:17:25 +08:00
|
|
|
|
const fabricObject = this.canvas
|
|
|
|
|
|
.getObjects()
|
|
|
|
|
|
.find((obj) => obj.id === layer.id);
|
2025-06-24 01:54:37 +08:00
|
|
|
|
if (fabricObject) {
|
|
|
|
|
|
fabricObject.erasable = flag;
|
|
|
|
|
|
fabricObject.set("erasable", flag);
|
|
|
|
|
|
fabricObject.setCoords(); // 更新控制点坐标
|
|
|
|
|
|
this.canvas.renderAll(); // 重新渲染画布
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-25 01:03:39 +08:00
|
|
|
|
async setCanvasSize(width, height) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.width = width;
|
|
|
|
|
|
this.height = height;
|
|
|
|
|
|
this.canvas.setWidth(width);
|
|
|
|
|
|
this.canvas.setHeight(height);
|
|
|
|
|
|
|
|
|
|
|
|
// 重置视图变换以确保元素位置正确
|
2025-06-25 01:03:39 +08:00
|
|
|
|
// this._resetViewportTransform();
|
|
|
|
|
|
if (this.canvas.getZoom() !== 1 || this.canvas.viewportTransform[0] !== 1) {
|
|
|
|
|
|
this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
|
|
|
|
|
|
await this.resetZoom();
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 居中所有画布元素,包括背景层和其他元素
|
|
|
|
|
|
this.centerAllObjects();
|
|
|
|
|
|
|
2025-07-10 01:01:46 +08:00
|
|
|
|
// // 重新渲染画布使变更生效
|
|
|
|
|
|
// this.canvas.renderAll();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 重置视图变换,使元素回到原始位置
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
2025-06-22 13:52:28 +08:00
|
|
|
|
_resetViewportTransform(zoom) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 保存当前缩放值
|
2025-06-22 13:52:28 +08:00
|
|
|
|
const currentZoom = zoom ?? this.canvas.getZoom();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 重置视图变换,但保留缩放级别
|
|
|
|
|
|
this.canvas.setViewportTransform([currentZoom, 0, 0, currentZoom, 0, 0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 居中所有画布元素
|
2025-06-18 11:05:23 +08:00
|
|
|
|
* 以背景层为参照,计算背景层的偏移量并应用到所有对象上
|
|
|
|
|
|
* 这样可以保持对象间的相对位置关系不变
|
2025-06-09 10:25:54 +08:00
|
|
|
|
*/
|
|
|
|
|
|
centerAllObjects() {
|
|
|
|
|
|
if (!this.canvas) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有可见对象(不是背景元素的对象)
|
|
|
|
|
|
const allObjects = this.canvas.getObjects();
|
|
|
|
|
|
if (allObjects.length === 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
const visibleObjects = allObjects.filter(
|
|
|
|
|
|
(obj) => obj.visible !== false && !obj.excludeFromExport
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 如果没有可见对象,直接返回
|
|
|
|
|
|
if (visibleObjects.length === 0) return;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 获取背景对象
|
2025-06-09 10:25:54 +08:00
|
|
|
|
const backgroundObject = visibleObjects.find((obj) => obj.isBackground);
|
|
|
|
|
|
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.canvas?.clipPath?.set?.({
|
|
|
|
|
|
left: this.width / 2,
|
|
|
|
|
|
top: this.height / 2,
|
|
|
|
|
|
originX: "center",
|
|
|
|
|
|
originY: "center",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas?.clipPath?.setCoords?.();
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 如果只有背景层或没有背景层,使用原有逻辑
|
|
|
|
|
|
if (!backgroundObject) {
|
|
|
|
|
|
console.warn("未找到背景层,使用默认居中逻辑");
|
|
|
|
|
|
// 如果只有一个对象且可能是背景,直接居中
|
|
|
|
|
|
if (visibleObjects.length === 1) {
|
|
|
|
|
|
const obj = visibleObjects[0];
|
|
|
|
|
|
obj.set({
|
|
|
|
|
|
left: this.width / 2,
|
|
|
|
|
|
top: this.height / 2,
|
|
|
|
|
|
originX: "center",
|
|
|
|
|
|
originY: "center",
|
|
|
|
|
|
});
|
|
|
|
|
|
obj.setCoords();
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 记录背景层居中前的位置
|
|
|
|
|
|
const backgroundOldLeft = backgroundObject.left;
|
|
|
|
|
|
const backgroundOldTop = backgroundObject.top;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 计算画布中心点
|
|
|
|
|
|
const canvasCenterX = this.width / 2;
|
|
|
|
|
|
const canvasCenterY = this.height / 2;
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 设置背景层居中
|
|
|
|
|
|
backgroundObject.set({
|
|
|
|
|
|
left: canvasCenterX,
|
|
|
|
|
|
top: canvasCenterY,
|
|
|
|
|
|
originX: "center",
|
|
|
|
|
|
originY: "center",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 计算背景层的偏移量
|
|
|
|
|
|
const deltaX = backgroundObject.left - backgroundOldLeft;
|
|
|
|
|
|
const deltaY = backgroundObject.top - backgroundOldTop;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 将相同的偏移量应用到所有其他对象上
|
2025-07-21 01:17:25 +08:00
|
|
|
|
const otherObjects = visibleObjects.filter(
|
|
|
|
|
|
(obj) => obj !== backgroundObject
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
otherObjects.forEach((obj) => {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
obj.set({
|
|
|
|
|
|
left: obj.left + deltaX,
|
|
|
|
|
|
top: obj.top + deltaY,
|
|
|
|
|
|
});
|
|
|
|
|
|
obj.setCoords(); // 更新对象的控制点坐标
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-07-14 23:42:28 +08:00
|
|
|
|
let isMaskLayer = false;
|
|
|
|
|
|
// 更新蒙层位置
|
|
|
|
|
|
this.layers.value.forEach((layer) => {
|
|
|
|
|
|
if (layer.clippingMask) {
|
|
|
|
|
|
isMaskLayer = true;
|
|
|
|
|
|
// 如果图层有遮罩,更新遮罩位置
|
|
|
|
|
|
layer.clippingMask.left += deltaX;
|
|
|
|
|
|
layer.clippingMask.top += deltaY;
|
2025-07-21 01:17:25 +08:00
|
|
|
|
|
|
|
|
|
|
if (layer.selectObject) {
|
|
|
|
|
|
// 如果有选区 则选区位置也要更新
|
|
|
|
|
|
layer.selectObject.left = layer.clippingMask.left;
|
|
|
|
|
|
layer.selectObject.top = layer.clippingMask.top;
|
|
|
|
|
|
const { object } = findObjectById(
|
|
|
|
|
|
this.canvas,
|
|
|
|
|
|
layer.selectObject?.id
|
|
|
|
|
|
);
|
|
|
|
|
|
object?.set({
|
|
|
|
|
|
left: layer.clippingMask.left,
|
|
|
|
|
|
top: layer.clippingMask.top,
|
|
|
|
|
|
});
|
|
|
|
|
|
object?.setCoords();
|
|
|
|
|
|
}
|
2025-07-14 23:42:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (isMaskLayer) {
|
2025-07-21 01:17:25 +08:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.layerManager?.updateLayersObjectsInteractivity?.(false, {
|
|
|
|
|
|
isMoveing: true,
|
|
|
|
|
|
});
|
2025-07-14 23:42:28 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 如果有背景层,更新蒙层位置
|
|
|
|
|
|
if (backgroundObject && CanvasConfig.isCropBackground) {
|
|
|
|
|
|
this.updateMaskPosition(backgroundObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新渲染画布
|
2025-07-10 01:01:46 +08:00
|
|
|
|
// this.canvas.renderAll();
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 计算多个对象的总边界框
|
|
|
|
|
|
* @private
|
|
|
|
|
|
* @param {Array} objects 要计算边界的对象数组
|
|
|
|
|
|
* @return {Object} 边界信息,包含left、top、width、height
|
|
|
|
|
|
*/
|
|
|
|
|
|
_calculateObjectsBounds(objects) {
|
|
|
|
|
|
if (!objects || objects.length === 0) return null;
|
|
|
|
|
|
|
|
|
|
|
|
let minX = Infinity;
|
|
|
|
|
|
let minY = Infinity;
|
|
|
|
|
|
let maxX = -Infinity;
|
|
|
|
|
|
let maxY = -Infinity;
|
|
|
|
|
|
|
|
|
|
|
|
objects.forEach((obj) => {
|
|
|
|
|
|
const bound = obj.getBoundingRect();
|
|
|
|
|
|
minX = Math.min(minX, bound.left);
|
|
|
|
|
|
minY = Math.min(minY, bound.top);
|
|
|
|
|
|
maxX = Math.max(maxX, bound.left + bound.width);
|
|
|
|
|
|
maxY = Math.max(maxY, bound.top + bound.height);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
left: minX,
|
|
|
|
|
|
top: minY,
|
|
|
|
|
|
width: maxX - minX,
|
|
|
|
|
|
height: maxY - minY,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setCanvasColor(color) {
|
|
|
|
|
|
this.backgroundColor = color;
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// this.canvas.setBackgroundColor(
|
|
|
|
|
|
// color,
|
|
|
|
|
|
// this.canvas.renderAll.bind(this.canvas)
|
|
|
|
|
|
// );
|
|
|
|
|
|
this.thumbnailManager?.generateLayerThumbnail?.(
|
|
|
|
|
|
this.layers?.value.find((layer) => layer.isBackground)?.id
|
2025-06-09 10:25:54 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 居中背景层
|
|
|
|
|
|
* @param {Object} backgroundLayerObject 背景层对象
|
|
|
|
|
|
* @param {Number} canvasWidth 画布宽度
|
|
|
|
|
|
* @param {Number} canvasHeight 画布高度
|
|
|
|
|
|
*/
|
|
|
|
|
|
centerBackgroundLayer(canvasWidth, canvasHeight) {
|
|
|
|
|
|
const backgroundLayerObject = this.getBackgroundLayer();
|
|
|
|
|
|
if (!backgroundLayerObject) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// const bgWidth = backgroundLayerObject.width * backgroundLayerObject.scaleX;
|
|
|
|
|
|
// const bgHeight =
|
|
|
|
|
|
// backgroundLayerObject.height * backgroundLayerObject.scaleY;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算居中位置
|
|
|
|
|
|
const left = canvasWidth / 2;
|
|
|
|
|
|
const top = canvasHeight / 2;
|
|
|
|
|
|
|
|
|
|
|
|
backgroundLayerObject.set({
|
|
|
|
|
|
left: left,
|
|
|
|
|
|
top: top,
|
|
|
|
|
|
originX: "center",
|
|
|
|
|
|
originY: "center",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
!CanvasConfig.isCropBackground && this.canvas.renderAll(); // 如果不需要裁剪背景层以外的内容,则渲染画布
|
|
|
|
|
|
|
|
|
|
|
|
// 如果需要裁剪背景层以外的内容,则更新蒙层位置
|
|
|
|
|
|
// 创建或更新蒙层
|
|
|
|
|
|
CanvasConfig.isCropBackground &&
|
2025-06-18 11:05:23 +08:00
|
|
|
|
!this.enabledRedGreenMode &&
|
2025-06-09 10:25:54 +08:00
|
|
|
|
this.createOrUpdateMask(backgroundLayerObject);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建或更新蒙层,用于裁剪不可见区域
|
|
|
|
|
|
* @param {Object} backgroundLayerObject 背景层对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
createOrUpdateMask(backgroundLayerObject) {
|
|
|
|
|
|
if (!backgroundLayerObject) return;
|
|
|
|
|
|
|
|
|
|
|
|
const bgWidth = backgroundLayerObject.width * backgroundLayerObject.scaleX;
|
2025-07-21 01:17:25 +08:00
|
|
|
|
const bgHeight =
|
|
|
|
|
|
backgroundLayerObject.height * backgroundLayerObject.scaleY;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
const left = backgroundLayerObject.left;
|
|
|
|
|
|
const top = backgroundLayerObject.top;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经存在蒙层,则更新它
|
|
|
|
|
|
if (this.maskLayer) {
|
|
|
|
|
|
this.canvas.remove(this.maskLayer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建蒙层 - 使用透明矩形作为裁剪区域
|
|
|
|
|
|
this.maskLayer = new fabric.Rect({
|
2025-06-18 11:05:23 +08:00
|
|
|
|
id: "canvasMaskLayer",
|
2025-06-09 10:25:54 +08:00
|
|
|
|
width: bgWidth,
|
|
|
|
|
|
height: bgHeight,
|
|
|
|
|
|
left: left,
|
|
|
|
|
|
top: top,
|
|
|
|
|
|
fill: "transparent",
|
2025-06-18 11:05:23 +08:00
|
|
|
|
stroke: "transparent",
|
2025-06-09 10:25:54 +08:00
|
|
|
|
strokeWidth: 1,
|
|
|
|
|
|
strokeDashArray: [5, 5],
|
|
|
|
|
|
selectable: false,
|
|
|
|
|
|
evented: false,
|
|
|
|
|
|
hoverCursor: "default",
|
|
|
|
|
|
originX: "center",
|
|
|
|
|
|
originY: "center",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 将蒙层添加到画布
|
|
|
|
|
|
this.canvas.add(this.maskLayer);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置蒙层为最顶层
|
|
|
|
|
|
this.maskLayer.bringToFront();
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.clipPath = new fabric.Rect({
|
|
|
|
|
|
width: bgWidth,
|
|
|
|
|
|
height: bgHeight,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
left: left,
|
|
|
|
|
|
top: top,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
originX: backgroundLayerObject.originX || "left",
|
|
|
|
|
|
originY: backgroundLayerObject.originY || "top",
|
|
|
|
|
|
absolutePositioned: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
getBackgroundLayer() {
|
|
|
|
|
|
if (!this.canvas) return null;
|
|
|
|
|
|
|
|
|
|
|
|
const backgroundLayer = this.canvas.getObjects().find((obj) => {
|
|
|
|
|
|
return obj.isBackground;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (backgroundLayer) return backgroundLayer;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有找到背景层,则根据图层ID查找
|
|
|
|
|
|
const backgroundLayerId = this.layers.value.find((layer) => {
|
|
|
|
|
|
return layer.isBackground;
|
|
|
|
|
|
})?.id;
|
|
|
|
|
|
|
|
|
|
|
|
const backgroundLayerByBgLayer = this.canvas.getObjects().find((obj) => {
|
|
|
|
|
|
return obj.isBackground || obj.id === backgroundLayerId;
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!backgroundLayerByBgLayer) {
|
2025-07-21 01:17:25 +08:00
|
|
|
|
console.warn(
|
|
|
|
|
|
"CanvasManager.js = >getBackgroundLayer 方法没有找到背景层"
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return backgroundLayerByBgLayer;
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新蒙层位置
|
|
|
|
|
|
* @param {Object} backgroundLayerObject 背景层对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
updateMaskPosition(backgroundLayerObject) {
|
2025-07-21 01:17:25 +08:00
|
|
|
|
if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath)
|
|
|
|
|
|
return;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
const left = backgroundLayerObject.left;
|
|
|
|
|
|
const top = backgroundLayerObject.top;
|
|
|
|
|
|
|
|
|
|
|
|
this.maskLayer.set({
|
|
|
|
|
|
left: left,
|
|
|
|
|
|
top: top,
|
|
|
|
|
|
width: backgroundLayerObject.width * backgroundLayerObject.scaleX,
|
|
|
|
|
|
height: backgroundLayerObject.height * backgroundLayerObject.scaleY,
|
|
|
|
|
|
originX: backgroundLayerObject.originX || "left",
|
|
|
|
|
|
originY: backgroundLayerObject.originY || "top",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.clipPath.set({
|
|
|
|
|
|
left: left,
|
|
|
|
|
|
top: top,
|
|
|
|
|
|
width: backgroundLayerObject.width * backgroundLayerObject.scaleX,
|
|
|
|
|
|
height: backgroundLayerObject.height * backgroundLayerObject.scaleY,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新指定图层的缩略图
|
|
|
|
|
|
* @param {String} layerId 图层ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
updateLayerThumbnail(layerId) {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.thumbnailManager?.generateLayerThumbnail?.(layerId);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新所有图层和元素的缩略图
|
|
|
|
|
|
*/
|
|
|
|
|
|
updateAllThumbnails() {
|
|
|
|
|
|
// 为所有元素生成缩略图
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this.thumbnailManager?.generateAllLayerThumbnails?.(this.layers.value);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
*
|
|
|
|
|
|
* 更改固定图层的图片
|
|
|
|
|
|
* @param {String} imageUrl 新的图片URL
|
|
|
|
|
|
* @param {Object} options 选项
|
|
|
|
|
|
* @param {String} options.targetLayerType 目标图层类型(background/fixed)
|
|
|
|
|
|
* @param {Boolean} options.undoable 是否可撤销,默认不可撤销
|
|
|
|
|
|
* @return {Object} 执行结果
|
|
|
|
|
|
* */
|
|
|
|
|
|
async changeFixedImage(imageUrl, options = {}) {
|
|
|
|
|
|
if (!this.layerManager) {
|
|
|
|
|
|
console.error("图层管理器未设置,无法更改固定图层图片");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-06-23 09:27:29 +08:00
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
const command = new ChangeFixedImageCommand({
|
|
|
|
|
|
canvas: this.canvas,
|
|
|
|
|
|
layerManager: this.layerManager,
|
|
|
|
|
|
imageUrl: imageUrl,
|
|
|
|
|
|
targetLayerType: options.targetLayerType || "fixed", // background/fixed
|
2025-06-23 09:27:29 +08:00
|
|
|
|
canvasWidth: this.canvasWidth,
|
|
|
|
|
|
canvasHeight: this.canvasHeight,
|
|
|
|
|
|
...options,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-07-21 01:17:25 +08:00
|
|
|
|
command.undoable =
|
|
|
|
|
|
options.undoable !== undefined ? options.undoable : false; // 默认不可撤销 undoable = true 为可撤销
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
2025-06-26 00:37:07 +08:00
|
|
|
|
const result = (await command?.execute?.()) || {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
layerId: null,
|
|
|
|
|
|
imageUrl: null,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const findLayer = this.layers.value.find((fItem) => {
|
|
|
|
|
|
if (options.targetLayerType === "fixed") {
|
|
|
|
|
|
return fItem.isFixed;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
2025-06-26 00:37:07 +08:00
|
|
|
|
if (options.targetLayerType === "background") {
|
|
|
|
|
|
return fItem.isBackground;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 如果找到了图层,则生成缩略图
|
|
|
|
|
|
findLayer && this.thumbnailManager?.generateLayerThumbnail(findLayer.id);
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-09 10:25:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 导出图片
|
|
|
|
|
|
* @param {Object} options 导出选项
|
|
|
|
|
|
* @param {Boolean} options.isContainBg 是否包含背景图层
|
|
|
|
|
|
* @param {Boolean} options.isContainFixed 是否包含固定图层
|
|
|
|
|
|
* @param {String} options.layerId 导出具体图层ID
|
|
|
|
|
|
* @param {Array} options.layerIdArray 导出多个图层ID数组
|
|
|
|
|
|
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
|
2025-06-18 11:05:23 +08:00
|
|
|
|
* @param {Boolean} options.restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
|
2025-06-09 10:25:54 +08:00
|
|
|
|
* @returns {String} 导出的图片数据URL
|
|
|
|
|
|
*/
|
|
|
|
|
|
exportImage(options = {}) {
|
|
|
|
|
|
if (!this.exportManager) {
|
|
|
|
|
|
console.error("导出管理器未初始化,请确保已设置图层管理器");
|
|
|
|
|
|
throw new Error("导出管理器未初始化");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-07-18 01:28:31 +08:00
|
|
|
|
// 如果当前有选中对象,先清除选中状态 否则导出有问题
|
|
|
|
|
|
this.canvas.discardActiveObject(); // 清除选中状态
|
|
|
|
|
|
this.canvas.renderAll(); // 重新渲染画布
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 自动设置红绿图模式相关参数
|
|
|
|
|
|
const enhancedOptions = {
|
|
|
|
|
|
...options,
|
|
|
|
|
|
// 如果没有明确指定,则根据当前模式自动设置
|
|
|
|
|
|
restoreOpacityInRedGreen:
|
2025-07-21 01:17:25 +08:00
|
|
|
|
options.restoreOpacityInRedGreen !== undefined
|
|
|
|
|
|
? options.restoreOpacityInRedGreen
|
|
|
|
|
|
: true, // 默认在红绿图模式下恢复透明度
|
2025-06-18 11:05:23 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层
|
|
|
|
|
|
if (
|
|
|
|
|
|
this.enabledRedGreenMode &&
|
|
|
|
|
|
!options.layerId &&
|
|
|
|
|
|
(!options.layerIdArray || options.layerIdArray.length === 0)
|
|
|
|
|
|
) {
|
|
|
|
|
|
console.log("检测到红绿图模式,自动包含所有普通图层进行导出");
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有非背景、非固定的普通图层ID
|
|
|
|
|
|
const normalLayerIds =
|
|
|
|
|
|
this.layers?.value
|
2025-07-21 01:17:25 +08:00
|
|
|
|
?.filter(
|
|
|
|
|
|
(layer) => !layer.isBackground && !layer.isFixed && layer.visible
|
|
|
|
|
|
)
|
2025-06-18 11:05:23 +08:00
|
|
|
|
?.map((layer) => layer.id) || [];
|
|
|
|
|
|
|
|
|
|
|
|
if (normalLayerIds.length > 0) {
|
|
|
|
|
|
enhancedOptions.layerIdArray = normalLayerIds;
|
|
|
|
|
|
console.log("红绿图模式导出图层:", normalLayerIds);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return this.exportManager.exportImage(enhancedOptions);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("CanvasManager导出图片失败:", error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dispose() {
|
|
|
|
|
|
// 释放导出管理器资源
|
|
|
|
|
|
if (this.exportManager) {
|
|
|
|
|
|
this.exportManager = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 释放事件管理器资源
|
|
|
|
|
|
if (this.eventManager) {
|
|
|
|
|
|
this.eventManager.dispose();
|
|
|
|
|
|
this.eventManager = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 释放动画管理器资源
|
|
|
|
|
|
if (this.animationManager) {
|
|
|
|
|
|
this.animationManager.dispose();
|
|
|
|
|
|
this.animationManager = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 释放缩略图管理器资源
|
|
|
|
|
|
if (this.thumbnailManager) {
|
|
|
|
|
|
this.thumbnailManager.dispose();
|
|
|
|
|
|
this.thumbnailManager = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.canvas) {
|
|
|
|
|
|
this.canvas.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getJSON() {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// // 简化图层数据,在loadJSON时要根据id恢复引用
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// const simplifyLayers = (layers) => {
|
|
|
|
|
|
// return layers.map((layer) => {
|
|
|
|
|
|
// if (layer?.children?.length) {
|
|
|
|
|
|
// layer.children = layer.children.map((child) => {
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// return {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// id: child.id,
|
|
|
|
|
|
// type: child.type,
|
|
|
|
|
|
// layerId: child.layerId,
|
|
|
|
|
|
// layerName: child.layerName,
|
|
|
|
|
|
// isBackground: child.isBackground,
|
|
|
|
|
|
// isLocked: child.isLocked,
|
|
|
|
|
|
// isVisible: child.isVisible,
|
|
|
|
|
|
// isFixed: child.isFixed,
|
|
|
|
|
|
// parentId: child.parentId,
|
|
|
|
|
|
// fabricObject: child.fabricObject
|
|
|
|
|
|
// ? {
|
|
|
|
|
|
// id: child.fabricObject.id,
|
|
|
|
|
|
// type: child.fabricObject.type,
|
|
|
|
|
|
// layerId: child.fabricObject.layerId,
|
|
|
|
|
|
// layerName: child.fabricObject.layerName,
|
|
|
|
|
|
// }
|
|
|
|
|
|
// : {},
|
|
|
|
|
|
// fabricObjects:
|
|
|
|
|
|
// child.fabricObjects?.map((obj) => ({
|
|
|
|
|
|
// id: obj.id,
|
|
|
|
|
|
// type: obj.type,
|
|
|
|
|
|
// layerId: obj.layerId,
|
|
|
|
|
|
// layerName: obj.layerName,
|
|
|
|
|
|
// })) || [],
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// };
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// });
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// }
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// return {
|
|
|
|
|
|
// id: layer.id,
|
|
|
|
|
|
// type: layer.type,
|
|
|
|
|
|
// layerId: layer.layerId,
|
|
|
|
|
|
// layerName: layer.layerName,
|
|
|
|
|
|
// isBackground: layer.isBackground,
|
|
|
|
|
|
// isLocked: layer.isLocked,
|
|
|
|
|
|
// isVisible: layer.isVisible,
|
|
|
|
|
|
// isFixed: layer.isFixed,
|
|
|
|
|
|
// parentId: layer.parentId,
|
|
|
|
|
|
// fabricObject: child.fabricObject
|
|
|
|
|
|
// ? {
|
|
|
|
|
|
// id: child.fabricObject.id,
|
|
|
|
|
|
// type: child.fabricObject.type,
|
|
|
|
|
|
// layerId: child.fabricObject.layerId,
|
|
|
|
|
|
// layerName: child.fabricObject.layerName,
|
|
|
|
|
|
// }
|
|
|
|
|
|
// : {},
|
|
|
|
|
|
// fabricObjects:
|
|
|
|
|
|
// child.fabricObjects?.map((obj) => ({
|
|
|
|
|
|
// id: obj.id,
|
|
|
|
|
|
// type: obj.type,
|
|
|
|
|
|
// layerId: obj.layerId,
|
|
|
|
|
|
// layerName: obj.layerName,
|
|
|
|
|
|
// })) || [],
|
|
|
|
|
|
// children: layer.children,
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// };
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// });
|
|
|
|
|
|
// };
|
2025-06-09 10:25:54 +08:00
|
|
|
|
try {
|
2025-07-14 01:00:23 +08:00
|
|
|
|
// 清除画布中选中状态
|
|
|
|
|
|
this.canvas.discardActiveObject();
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
|
2025-07-21 01:17:25 +08:00
|
|
|
|
const simplifyLayersData = simplifyLayers(
|
|
|
|
|
|
JSON.parse(JSON.stringify(this.layers.value))
|
|
|
|
|
|
);
|
2025-06-22 13:52:28 +08:00
|
|
|
|
console.log("获取画布JSON数据...", simplifyLayersData);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
return JSON.stringify({
|
|
|
|
|
|
canvas: this.canvas.toJSON([
|
|
|
|
|
|
"id",
|
2025-06-18 11:05:23 +08:00
|
|
|
|
"type",
|
2025-06-09 10:25:54 +08:00
|
|
|
|
"layerId",
|
|
|
|
|
|
"layerName",
|
|
|
|
|
|
"isBackground",
|
2025-06-18 11:05:23 +08:00
|
|
|
|
"isLocked",
|
|
|
|
|
|
"isVisible",
|
2025-06-09 10:25:54 +08:00
|
|
|
|
"isFixed",
|
|
|
|
|
|
"parentId",
|
2025-06-18 11:05:23 +08:00
|
|
|
|
"eraser",
|
|
|
|
|
|
"eraserable",
|
|
|
|
|
|
"erasable",
|
2025-07-21 01:17:25 +08:00
|
|
|
|
"customType",
|
2025-06-09 10:25:54 +08:00
|
|
|
|
]),
|
2025-07-14 23:42:28 +08:00
|
|
|
|
layers: simplifyLayersData, // 简化图层数据
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// layers: JSON.stringify(JSON.parse(JSON.stringify(this.layers.value))), // 全数据
|
2025-06-09 10:25:54 +08:00
|
|
|
|
version: "1.0", // 添加版本信息
|
|
|
|
|
|
timestamp: new Date().toISOString(), // 添加时间戳
|
|
|
|
|
|
canvasWidth: this.canvasWidth.value,
|
|
|
|
|
|
canvasHeight: this.canvasHeight.value,
|
|
|
|
|
|
canvasColor: this.canvasColor.value,
|
2025-06-22 13:52:28 +08:00
|
|
|
|
activeLayerId: this.layerManager?.activeLayerId?.value,
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("获取画布JSON失败:", error);
|
|
|
|
|
|
throw new Error("获取画布JSON失败");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
loadJSON(json, calllBack) {
|
2025-06-09 10:25:54 +08:00
|
|
|
|
console.log("加载画布JSON数据:", json);
|
|
|
|
|
|
|
|
|
|
|
|
// 确保传入的json是字符串格式
|
|
|
|
|
|
if (typeof json === "object") {
|
|
|
|
|
|
json = JSON.stringify(json);
|
|
|
|
|
|
} else if (typeof json !== "string") {
|
|
|
|
|
|
throw new Error("loadJSON方法需要传入字符串或对象格式的JSON数据");
|
|
|
|
|
|
}
|
|
|
|
|
|
// 解析JSON字符串
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsedJson = JSON.parse(json);
|
2025-07-10 01:01:46 +08:00
|
|
|
|
this.canvasWidth.value = parsedJson.canvasWidth || this.width;
|
|
|
|
|
|
this.canvasHeight.value = parsedJson.canvasHeight || this.height;
|
|
|
|
|
|
this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
2025-07-14 01:00:23 +08:00
|
|
|
|
// eslint-disable-next-line no-async-promise-executor
|
2025-06-22 13:52:28 +08:00
|
|
|
|
return new Promise(async (resolve, reject) => {
|
2025-07-14 23:42:28 +08:00
|
|
|
|
const tempLayers = parsedJson?.layers || [];
|
2025-06-09 10:25:54 +08:00
|
|
|
|
const canvasData = parsedJson?.canvas;
|
|
|
|
|
|
|
|
|
|
|
|
if (!tempLayers) {
|
|
|
|
|
|
reject(new Error("JSON数据中缺少layers字段"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!canvasData) {
|
|
|
|
|
|
reject(new Error("JSON数据中缺少canvas字段"));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.layers.value = tempLayers;
|
|
|
|
|
|
|
|
|
|
|
|
// this.canvasWidth.value = parsedJson.canvasWidth || this.width;
|
|
|
|
|
|
// this.canvasHeight.value = parsedJson.canvasHeight || this.height;
|
|
|
|
|
|
// this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor;
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
console.log("是否检测到红绿图模式内容:", this.enabledRedGreenMode);
|
|
|
|
|
|
|
|
|
|
|
|
// 重置视图变换以确保元素位置正确
|
2025-06-22 13:52:28 +08:00
|
|
|
|
this._resetViewportTransform(1);
|
|
|
|
|
|
let canvasClipPath = null;
|
|
|
|
|
|
// 克隆当前裁剪路径
|
|
|
|
|
|
if (this.canvas?.clipPath) {
|
|
|
|
|
|
canvasClipPath = this.canvas?.clipPath;
|
|
|
|
|
|
}
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 清除当前画布内容
|
2025-06-26 00:37:07 +08:00
|
|
|
|
// this.canvas.clear(); // 清除画布内容 可以先去掉 这样加载闪动的情况就比较少 如果有问题 可以再打开
|
2025-06-22 13:52:28 +08:00
|
|
|
|
console.log("清除当前画布内容", canvasData);
|
|
|
|
|
|
delete canvasData.clipPath; // 删除当前裁剪路径
|
2025-06-09 10:25:54 +08:00
|
|
|
|
// 加载画布数据
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.canvas.loadFromJSON(canvasData, async () => {
|
|
|
|
|
|
await optimizeCanvasRendering(this.canvas, async () => {
|
2025-06-22 13:52:28 +08:00
|
|
|
|
// 清空重做栈
|
|
|
|
|
|
this.commandManager?.clear?.();
|
2025-06-18 11:05:23 +08:00
|
|
|
|
this.backgroundColor = parsedJson.backgroundColor || "#ffffff";
|
2025-06-22 13:52:28 +08:00
|
|
|
|
if (canvasClipPath) {
|
|
|
|
|
|
// canvasClipPath.set({
|
|
|
|
|
|
// absolutePositioned: true,
|
|
|
|
|
|
// });
|
|
|
|
|
|
this.canvas.clipPath = canvasClipPath;
|
|
|
|
|
|
// await new Promise((resolve) => {
|
|
|
|
|
|
// debugger;
|
|
|
|
|
|
// fabric.util.enlivenObjects([canvasClipPath], (clipPaths) => {
|
|
|
|
|
|
// if (clipPaths && clipPaths.length > 0) {
|
|
|
|
|
|
// resolve(clipPaths[0]);
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// resolve(null);
|
|
|
|
|
|
// }
|
|
|
|
|
|
// });
|
|
|
|
|
|
// });
|
|
|
|
|
|
// debugger;
|
|
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 重置画布数据
|
|
|
|
|
|
this.setCanvasSize(this.canvas.width, this.canvas.height);
|
|
|
|
|
|
// 重新构建对象关系
|
2025-07-14 23:42:28 +08:00
|
|
|
|
// restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects());
|
2025-06-18 11:05:23 +08:00
|
|
|
|
// 验证图层关联关系 - 稳定后可以注释
|
2025-07-14 23:42:28 +08:00
|
|
|
|
// const isValidate = validateLayerAssociations(
|
|
|
|
|
|
// this.layers.value,
|
|
|
|
|
|
// this.canvas.getObjects()
|
|
|
|
|
|
// );
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
2025-07-14 23:42:28 +08:00
|
|
|
|
// console.log("图层关联验证结果:", isValidate);
|
2025-06-25 01:03:39 +08:00
|
|
|
|
// 排序
|
|
|
|
|
|
// 使用LayerSort工具重新排列画布对象(如果可用)
|
|
|
|
|
|
await this?.layerManager?.layerSort?.rearrangeObjects();
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
2025-07-21 01:17:25 +08:00
|
|
|
|
this.layerManager.activeLayerId.value = this.layers.value[0]
|
|
|
|
|
|
.children?.length
|
2025-07-11 00:26:38 +08:00
|
|
|
|
? this.layers.value[0].children[0].id
|
|
|
|
|
|
: this.layers.value[0]?.id || parsedJson?.activeLayerId || null;
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
// // 如果检测到红绿图模式内容,进行缩放调整
|
|
|
|
|
|
// if (this.enabledRedGreenMode) {
|
|
|
|
|
|
// this._rescaleRedGreenModeContent();
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// 重载代码后支持回调中操作一些内容
|
|
|
|
|
|
await calllBack?.();
|
|
|
|
|
|
|
|
|
|
|
|
// 确保所有对象的交互性正确设置
|
2025-07-21 01:17:25 +08:00
|
|
|
|
await this.layerManager?.updateLayersObjectsInteractivity?.(
|
|
|
|
|
|
false
|
|
|
|
|
|
);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
console.log(this.layerManager.layers.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新所有缩略图
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.updateAllThumbnails();
|
2025-06-22 13:52:28 +08:00
|
|
|
|
}, 500);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
|
console.log("画布JSON数据加载完成");
|
2025-06-26 00:37:07 +08:00
|
|
|
|
// 画布初始化事件
|
2025-07-04 03:16:18 +08:00
|
|
|
|
this.handleCanvasInit?.(true);
|
2025-06-18 11:05:23 +08:00
|
|
|
|
resolve();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("恢复图层数据失败:", error);
|
|
|
|
|
|
reject(new Error("恢复图层数据失败: " + error.message));
|
2025-06-09 10:25:54 +08:00
|
|
|
|
}
|
2025-06-18 11:05:23 +08:00
|
|
|
|
});
|
2025-06-09 10:25:54 +08:00
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("解析JSON失败:", error);
|
|
|
|
|
|
throw new Error("解析JSON失败,请检查输入格式: " + error.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 缩放红绿图模式内容以适应当前画布大小
|
|
|
|
|
|
* 确保衣服底图和红绿图永远在画布内可见
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_rescaleRedGreenModeContent() {
|
|
|
|
|
|
if (!this.canvas) return;
|
|
|
|
|
|
|
|
|
|
|
|
console.log("正在重新缩放红绿图内容...");
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取固定图层和普通图层
|
|
|
|
|
|
const fixedLayerObject = this._getFixedLayerObject();
|
|
|
|
|
|
const normalLayerObjects = this._getNormalLayerObjects();
|
|
|
|
|
|
|
|
|
|
|
|
if (!fixedLayerObject) {
|
|
|
|
|
|
console.warn("找不到固定图层对象,无法进行红绿图内容缩放");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算边距(画布两侧各留出一定空间)
|
|
|
|
|
|
const margin = 50;
|
|
|
|
|
|
const maxWidth = this.canvas.width - margin * 2;
|
|
|
|
|
|
const maxHeight = this.canvas.height - margin * 2;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算原始尺寸
|
|
|
|
|
|
const originalWidth = fixedLayerObject.width * fixedLayerObject.scaleX;
|
|
|
|
|
|
const originalHeight = fixedLayerObject.height * fixedLayerObject.scaleY;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算需要的缩放比例,确保图像完全适应画布
|
|
|
|
|
|
const scaleX = maxWidth / originalWidth;
|
|
|
|
|
|
const scaleY = maxHeight / originalHeight;
|
|
|
|
|
|
const scale = Math.min(scaleX, scaleY);
|
|
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`计算的缩放比例: ${scale},原始尺寸: ${originalWidth}x${originalHeight},目标尺寸: ${maxWidth}x${maxHeight}`
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果缩放比例接近1,不进行缩放
|
|
|
|
|
|
if (Math.abs(scale - 1) < 0.05) {
|
|
|
|
|
|
console.log("缩放比例接近1,不进行缩放,仅居中内容");
|
|
|
|
|
|
this.centerAllObjects();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 缩放固定图层(衣服底图)
|
|
|
|
|
|
this._rescaleObject(fixedLayerObject, scale);
|
|
|
|
|
|
|
|
|
|
|
|
// 缩放所有普通图层对象(红绿图和其他内容)
|
|
|
|
|
|
normalLayerObjects.forEach((obj) => {
|
|
|
|
|
|
// 红绿图对象应与底图保持完全一致的位置和大小
|
|
|
|
|
|
if (this._isLikelyRedGreenImage(obj, fixedLayerObject)) {
|
|
|
|
|
|
// 完全匹配底图的位置和大小
|
|
|
|
|
|
obj.set({
|
|
|
|
|
|
scaleX: fixedLayerObject.scaleX,
|
|
|
|
|
|
scaleY: fixedLayerObject.scaleY,
|
|
|
|
|
|
left: fixedLayerObject.left,
|
|
|
|
|
|
top: fixedLayerObject.top,
|
|
|
|
|
|
originX: fixedLayerObject.originX || "center",
|
|
|
|
|
|
originY: fixedLayerObject.originY || "center",
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 其他普通对象进行等比例缩放
|
|
|
|
|
|
this._rescaleObject(obj, scale);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 重新居中所有内容
|
|
|
|
|
|
this.centerAllObjects();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新所有对象的坐标系统
|
|
|
|
|
|
this.canvas.getObjects().forEach((obj) => {
|
|
|
|
|
|
obj.setCoords();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染画布
|
|
|
|
|
|
this.canvas.renderAll();
|
|
|
|
|
|
|
|
|
|
|
|
console.log("红绿图内容缩放完成");
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("缩放红绿图内容时出错:", error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 缩放单个对象
|
|
|
|
|
|
* @param {Object} obj fabric对象
|
|
|
|
|
|
* @param {Number} scale 缩放比例
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_rescaleObject(obj, scale) {
|
|
|
|
|
|
if (!obj) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 保存原始中心点
|
|
|
|
|
|
const center = obj.getCenterPoint();
|
|
|
|
|
|
|
|
|
|
|
|
// 应用新的缩放
|
|
|
|
|
|
obj.set({
|
|
|
|
|
|
scaleX: obj.scaleX * scale,
|
|
|
|
|
|
scaleY: obj.scaleY * scale,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 重新定位到原中心点
|
|
|
|
|
|
obj.setPositionByOrigin(center, "center", "center");
|
|
|
|
|
|
obj.setCoords();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取固定图层对象(衣服底图)
|
|
|
|
|
|
* @returns {Object|null} 固定图层对象或null
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_getFixedLayerObject() {
|
|
|
|
|
|
if (!this.layers || !this.layers.value) return null;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找固定图层
|
|
|
|
|
|
const fixedLayer = this.layers.value.find((layer) => layer.isFixed);
|
|
|
|
|
|
if (!fixedLayer) return null;
|
|
|
|
|
|
|
|
|
|
|
|
// 返回图层中的fabric对象
|
|
|
|
|
|
return fixedLayer.fabricObject || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取所有普通图层对象(包括红绿图)
|
|
|
|
|
|
* @returns {Array} 普通图层对象数组
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_getNormalLayerObjects() {
|
|
|
|
|
|
if (!this.layers || !this.layers.value) return [];
|
|
|
|
|
|
|
|
|
|
|
|
// 查找所有非背景、非固定的普通图层
|
2025-07-21 01:17:25 +08:00
|
|
|
|
const normalLayers = this.layers.value.filter(
|
|
|
|
|
|
(layer) => !layer.isBackground && !layer.isFixed
|
|
|
|
|
|
);
|
2025-06-09 10:25:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 收集所有普通图层中的对象
|
|
|
|
|
|
const objects = [];
|
|
|
|
|
|
normalLayers.forEach((layer) => {
|
|
|
|
|
|
// 如果有单个对象属性
|
|
|
|
|
|
if (layer.fabricObject) {
|
|
|
|
|
|
objects.push(layer.fabricObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有对象数组
|
|
|
|
|
|
if (Array.isArray(layer.fabricObjects)) {
|
|
|
|
|
|
layer.fabricObjects.forEach((obj) => {
|
|
|
|
|
|
if (obj) objects.push(obj);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return objects;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 判断对象是否可能是红绿图
|
|
|
|
|
|
* 通过比较与衣服底图的大小、位置来判断
|
|
|
|
|
|
* @param {Object} obj 要检查的对象
|
|
|
|
|
|
* @param {Object} fixedLayerObject 固定图层对象(衣服底图)
|
|
|
|
|
|
* @returns {Boolean} 是否可能是红绿图
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
_isLikelyRedGreenImage(obj, fixedLayerObject) {
|
|
|
|
|
|
if (!obj || !fixedLayerObject) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查对象是否为图像
|
|
|
|
|
|
if (obj.type !== "image") return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 比较尺寸(允许5%的误差)
|
|
|
|
|
|
const sizeMatch =
|
2025-07-21 01:17:25 +08:00
|
|
|
|
Math.abs(
|
|
|
|
|
|
obj.width * obj.scaleX -
|
|
|
|
|
|
fixedLayerObject.width * fixedLayerObject.scaleX
|
|
|
|
|
|
) <
|
2025-06-09 10:25:54 +08:00
|
|
|
|
fixedLayerObject.width * fixedLayerObject.scaleX * 0.05 &&
|
2025-07-21 01:17:25 +08:00
|
|
|
|
Math.abs(
|
|
|
|
|
|
obj.height * obj.scaleY -
|
|
|
|
|
|
fixedLayerObject.height * fixedLayerObject.scaleY
|
|
|
|
|
|
) <
|
2025-06-09 10:25:54 +08:00
|
|
|
|
fixedLayerObject.height * fixedLayerObject.scaleY * 0.05;
|
|
|
|
|
|
|
|
|
|
|
|
// 比较位置(允许一定的偏差)
|
|
|
|
|
|
const positionMatch =
|
|
|
|
|
|
Math.abs(obj.left - fixedLayerObject.left) < 50 &&
|
|
|
|
|
|
Math.abs(obj.top - fixedLayerObject.top) < 50;
|
|
|
|
|
|
|
|
|
|
|
|
return sizeMatch && positionMatch;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|