Files
aida_front/src/component/Canvas/CanvasEditor/managers/CanvasManager.js

1912 lines
59 KiB
JavaScript
Raw Normal View History

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 {
findLayerRecursively,
2025-06-09 10:25:54 +08:00
isGroupLayer,
OperationType,
OperationTypes,
2026-01-02 11:24:11 +08:00
findLayer,
createLayer,
LayerType,
SpecialLayerId,
2026-01-05 11:47:36 +08:00
BlendMode,
2025-06-09 10:25:54 +08:00
} from "../utils/layerHelper";
2026-01-02 11:24:11 +08:00
import { ObjectMoveCommand } from "../commands/ObjectCommands";
2025-06-09 10:25:54 +08:00
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,
2026-01-02 11:24:11 +08:00
palletToFill,
fillToCssStyle,
calculateRotatedTopLeftDeg,
2026-01-06 14:17:04 +08:00
calculateCenterPoint,
2026-01-02 11:24:11 +08:00
createPatternTransform,
2026-01-06 14:17:04 +08:00
getTransformScaleAngle,
2026-01-05 11:47:36 +08:00
base64ToCanvas,
2025-07-21 01:17:25 +08:00
} 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";
2026-01-02 11:24:11 +08:00
import { getObjectAlphaToCanvas } from "../utils/objectHelper";
import { AddLayerCommand } from "../commands/LayerCommands";
import { fa, id } from "element-plus/es/locales.mjs";
import i18n from "@/lang/index.ts";
const {t} = i18n.global;
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; // 图层引用
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; // 是否启用红绿图模式
this.isFixedErasable = options.isFixedErasable || false; // 是否允许擦除固定图层
2025-06-18 11:05:23 +08:00
this.eraserStateManager = null; // 橡皮擦状态管理器引用
this.handleCanvasInit = null; // 画布初始化回调函数
2026-01-02 11:24:11 +08:00
this.props = options.props || {};
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; // 将缩略图管理器绑定到画布
2026-01-02 11:24:11 +08:00
// 设置画布辅助线
initAligningGuidelines(this.canvas);
2025-06-09 10:25:54 +08:00
2026-01-02 11:24:11 +08:00
// 设置画布中心线
// initCenteringGuidelines(this.canvas);
2025-06-09 10:25:54 +08:00
// 初始化画布事件监听器
this._initCanvasEvents();
}
// 添加图像到指定图层
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,
});
}
// 执行高保真合并操作
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) => {
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-07-21 01:17:25 +08:00
const fixedLayer = this.layers?.value?.find(
(layer) => layer.isFixed && !layer.locked
);
// 如果有固定图层且未锁定,则生成缩略图
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,
lastSelectLayerId: this.lastSelectLayerId,
2025-06-09 10:25:54 +08:00
});
// 设置动画交互效果
this.animationManager.setupInteractionAnimations();
}
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();
}
// 设置固定图层可擦除状态
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);
if (fabricObject) {
fabricObject.erasable = flag;
fabricObject.set("erasable", flag);
fabricObject.setCoords(); // 更新控制点坐标
this.canvas.renderAll(); // 重新渲染画布
}
return;
}
}
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);
// 重置视图变换以确保元素位置正确
// 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();
// // 重新渲染画布使变更生效
// 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
*/
2026-01-02 11:24:11 +08:00
async centerAllObjects() {
2025-06-09 10:25:54 +08:00
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);
2026-01-02 11:24:11 +08:00
// !this.canvas?.clipPath &&
// this.centerBackgroundLayer(this.canvas.width, this.canvas.height);
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-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(); // 更新对象的控制点坐标
});
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();
}
}
});
if (isMaskLayer) {
2025-07-21 01:17:25 +08:00
setTimeout(() => {
this.layerManager?.updateLayersObjectsInteractivity?.(false, {
isMoveing: true,
});
});
}
2025-06-09 10:25:54 +08:00
// 如果有背景层,更新蒙层位置
if (backgroundObject && CanvasConfig.isCropBackground) {
this.updateMaskPosition(backgroundObject);
}
2026-01-02 11:24:11 +08:00
// 更新颜色层信息
2026-01-06 14:17:04 +08:00
const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR);
if(colorObject){
await this.setObjecCliptInfo(colorObject);
}
2026-01-02 11:24:11 +08:00
const groupLayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP);
2026-01-05 11:47:36 +08:00
if(groupLayer){
2026-01-02 11:24:11 +08:00
const groupRect = new fabric.Rect({});
2026-01-05 11:47:36 +08:00
await this.setObjecCliptInfo(groupRect);
2026-01-02 11:24:11 +08:00
groupLayer.clippingMask = groupRect.toObject();
}
2025-06-09 10:25:54 +08:00
// 重新渲染画布
2026-01-02 11:24:11 +08:00
this.canvas.renderAll();
2025-06-09 10:25:54 +08:00
}
/**
* 计算多个对象的总边界框
* @private
* @param {Array} objects 要计算边界的对象数组
* @return {Object} 边界信息包含lefttopwidthheight
*/
_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 画布高度
*/
2026-01-02 11:24:11 +08:00
async centerBackgroundLayer(canvasWidth, canvasHeight) {
2025-06-09 10:25:54 +08:00
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);
}
2026-01-02 11:24:11 +08:00
this.canvas.getObjects().forEach((obj) => {
if (obj.id === "canvasMaskLayer") {
this.canvas.remove(obj);
}
})
2025-06-09 10:25:54 +08:00
// 创建蒙层 - 使用透明矩形作为裁剪区域
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;
}
2026-01-02 11:24:11 +08:00
getFixedLayerObject() {
if (!this.canvas) return null;
const fixedLayer = this.canvas.getObjects().find((obj) => {
return obj.isFixed;
});
if (fixedLayer) return fixedLayer;
// 如果没有找到固定层则根据图层ID查找
const fixedLayerId = this.layers.value.find((layer) => {
return layer.isFixed;
})?.id;
const fixedLayerByFixedLayer = this.canvas.getObjects().find((obj) => {
return obj.isFixed || obj.id === fixedLayerId;
});
if (!fixedLayerByFixedLayer) {
console.warn(
"CanvasManager.js = >getFixedLayerObject 方法没有找到固定层"
);
}
return fixedLayerByFixedLayer;
}
getBackgroundLayerObject() {
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) {
console.warn(
"CanvasManager.js = >getBackgroundLayerObject 方法没有找到背景层"
);
}
return backgroundLayerByBgLayer;
}
getLayerObjectById(layerId) {
if (!this.canvas) return null;
const layerObject = this.canvas.getObjects().find((obj) => {
return obj.id === layerId;
});
if (layerObject) return layerObject;
// 如果没有找到图层对象则根据图层ID查找
const layerObjectByLayerId = this.canvas.getObjects().find((obj) => {
return obj.id === layerId;
});
if (!layerObjectByLayerId) {
console.warn(
"CanvasManager.js = >getLayerObjectById 方法没有找到图层对象"
);
}
return layerObjectByLayerId;
}
2026-01-06 14:17:04 +08:00
getObjectsByIds(ids){
const objects = this.canvas.getObjects().filter((obj) => {
return ids.includes(obj.id);
});
return objects;
}
2025-06-09 10:25:54 +08:00
/**
* 更新蒙层位置
* @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
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
}
if (options.targetLayerType === "background") {
return fItem.isBackground;
}
return false;
});
// 如果找到了图层,则生成缩略图
findLayer && this.thumbnailManager?.generateLayerThumbnail(findLayer.id);
2026-01-02 11:24:11 +08:00
this.layerManager?.sortLayers?.();
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 是否包含固定图层
2026-01-05 11:47:36 +08:00
* @param {Boolean} options.isContainFixedOther 是否包含其他固定图层
2025-06-09 10:25:54 +08:00
* @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
* @param {Boolean} options.isEnhanceImg 是否是增强图片
2026-01-02 11:24:11 +08:00
* @param {Boolean} options.isCropByBg 是否使用背景大小裁剪
2025-06-09 10:25:54 +08:00
* @returns {String} 导出的图片数据URL
*/
async exportImage(options = {}) {
2025-06-09 10:25:54 +08:00
if (!this.exportManager) {
console.error("导出管理器未初始化,请确保已设置图层管理器");
throw new Error("导出管理器未初始化");
}
try {
// 如果当前有选中对象,先清除选中状态 否则导出有问题
2025-11-07 09:43:09 +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
: false, // 默认在红绿图模式下恢复透明度
2026-01-06 14:17:04 +08:00
excludedLayers: [SpecialLayerId.SPECIAL_GROUP],
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(
2026-01-05 11:47:36 +08:00
(layer) => !layer.isBackground && !layer.isFixed && !layer.isFixedOther && layer.visible
2025-07-21 01:17:25 +08:00
)
2025-06-18 11:05:23 +08:00
?.map((layer) => layer.id) || [];
if (normalLayerIds.length > 0) {
enhancedOptions.layerIdArray = normalLayerIds;
console.log("红绿图模式导出图层:", normalLayerIds);
}
}
return await this.exportManager.exportImage(enhancedOptions);
2025-06-09 10:25:54 +08:00
} catch (error) {
2026-01-02 11:24:11 +08:00
console.warn("CanvasManager导出图片失败:", error);
2025-06-09 10:25:54 +08:00
throw error;
}
}
2026-01-06 14:17:04 +08:00
/**
* 导出所有信息
* @returns {Object} 包含所有图层信息的对象
*/
async exportAllInfo() {
// 导出颜色图层信息
const color = await this.exportColorLayer().catch(() => (null));
// 导出印花和元素图层信息
const printTrimsData = await this.exportPrintTrimsLayers().catch(() => ({prints: null, trims: null}));
return {
color,
...printTrimsData,
};
}
2026-01-02 11:24:11 +08:00
/**
* 导出颜色图层
* @returns {Object} 导出的颜色图层数据URL
*/
async exportColorLayer() {
if (!this.exportManager) {
console.warn("导出管理器未初始化,请确保已设置图层管理器");
return Promise.reject("颜色图层不存在");
}
const object = this.getLayerObjectById(SpecialLayerId.COLOR);
if(!object){
console.warn("颜色图层不存在,请确保已添加颜色图层");
return Promise.reject("颜色图层不存在");
}
2026-01-06 14:17:04 +08:00
const css = fillToCssStyle(object.fill)
2026-01-02 11:24:11 +08:00
const canvas = new fabric.StaticCanvas();
canvas.setDimensions({
width: object.width,
height: object.height,
backgroundColor: null,
imageSmoothingEnabled: true,
});
const cloneObject = await new Promise((resolve, reject) => {
object.clone(resolve);
});
cloneObject.set({
left: canvas.width / 2,
top: canvas.height / 2,
scaleX: 1,
scaleY: 1,
visible: true,
clipPath: null,
});
canvas.add(cloneObject);
canvas.renderAll();
const base64 = canvas.toDataURL({
format: "png",
quality: 1,
});
canvas.clear();
2026-01-06 14:17:04 +08:00
const color = object.originColor;
return {css, base64, color};
2026-01-02 11:24:11 +08:00
}
2026-01-06 14:17:04 +08:00
/**
* 导出印花和元素图层
*/
async exportPrintTrimsLayers() {
const object = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP);
if(!object) return Promise.reject("印花和元素图层组不存在");
const ids = object.children.map((v) => v.id);
const objects = this.getObjectsByIds(ids).filter((v) => !!v.sourceData);
const fixedLayerObj = this.getFixedLayerObject();
if(!fixedLayerObj) return Promise.reject("固定图层不存在");
const flWidth = fixedLayerObj.width
const flHeight = fixedLayerObj.height
const flTop = fixedLayerObj.top
const flLeft = fixedLayerObj.left
const flScaleX = fixedLayerObj.scaleX
const flScaleY = fixedLayerObj.scaleY
const prints = [];
const trims = [];
objects.forEach((v) => {
const obj = {
ifSingle: v.sourceData.ifSingle,
level2Type: v.sourceData.level2Type,
designType: v.sourceData.designType,
path: v.sourceData.path,
location: [0, 0],
scale: [0, 0],
angle: v.angle,
name: v.sourceData.name,
}
if(obj.ifSingle){
let left = (v.left - (flLeft - flWidth * flScaleX / 2));
let top = (v.top - (flTop - flHeight * flScaleY / 2));
let width = (v.width * v.scaleX);
let height = (v.height * v.scaleY);
let {x:cx, y:cy} = calculateCenterPoint(width, height, left, top, v.angle);
let x = (cx-width/2) / flScaleX;
let y = (cy-height/2) / flScaleY;
obj.location = [x, y];
obj.scale = [(v.width * v.scaleX) / (flWidth * flScaleX), (v.height * v.scaleY) / (flHeight * flScaleY)];
}else{
let fill = v.fill;
let fill_ = v.fill_;
if(!fill || !fill_) return;
let {scale, angle} = getTransformScaleAngle(fill.patternTransform);
let scaleX = scale * 5 * v.fill_.width / flWidth;
let scaleY = scale * 5 * v.fill_.height / flHeight;
let scaleXY = flWidth > flHeight ? scaleX : scaleY;
let left = fill.offsetX + v.fill_.width * scale / 2;
let top = fill.offsetY + v.fill_.height * scale / 2;
obj.scale = [scaleXY, scaleXY];
obj.angle = angle;
obj.location = [left, top];
}
if(obj.level2Type === "Pattern"){
prints.push(obj);
}else if(obj.level2Type === "Embroidery"){
trims.push(obj);
}
})
return {prints, trims};
}
2025-06-09 10:25:54 +08:00
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 {
// 清除画布中选中状态
2026-01-02 11:24:11 +08:00
// this.canvas.discardActiveObject();
this.canvas.renderAll();
2026-01-06 15:09:02 +08:00
// 排除颜色图层和特殊组图层
const excludedLayers = [SpecialLayerId.COLOR, SpecialLayerId.SPECIAL_GROUP];
this.layers.value.forEach((layer) => {
if(excludedLayers.includes(layer.id)){
excludedLayers.push(...layer.children?.map((child) => child.id));
}
})
const canvas = this.canvas.toJSON([
"id",
"type",
"layerId",
"layerName",
"isBackground",
"isLocked",
"isVisible",
"isFixed",
"parentId",
"eraser",
"eraserable",
"erasable",
"customType",
"fill_",
"scaleX",
"scaleY",
"top",
"left",
"width",
"height",
]);
canvas.objects = canvas.objects.filter((v) => !excludedLayers.includes(v.layerId));
2025-07-21 01:17:25 +08:00
const simplifyLayersData = simplifyLayers(
2026-01-06 15:09:02 +08:00
JSON.parse(JSON.stringify(this.layers.value)),
excludedLayers
2025-07-21 01:17:25 +08:00
);
2026-01-06 15:09:02 +08:00
const data = {
canvas,
layers: simplifyLayersData, // 简化图层数据
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,
2026-01-06 15:09:02 +08:00
};
2026-01-02 11:24:11 +08:00
console.log("获取画布JSON数据...", data);
2026-01-06 15:09:02 +08:00
return JSON.stringify(data);
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);
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
// eslint-disable-next-line no-async-promise-executor
2025-06-22 13:52:28 +08:00
return new Promise(async (resolve, reject) => {
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
// 清除当前画布内容
// 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";
// 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 {
// 重置画布数据
2026-01-02 11:24:11 +08:00
await this.setCanvasSize(this.canvas.width, this.canvas.height);
await this.centerBackgroundLayer(this.canvas.width, this.canvas.height);
await this.createOtherLayers(this.props.otherData);
2025-06-18 11:05:23 +08:00
// 重新构建对象关系
// restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects());
2025-06-18 11:05:23 +08:00
// 验证图层关联关系 - 稳定后可以注释
// const isValidate = validateLayerAssociations(
// this.layers.value,
// this.canvas.getObjects()
// );
2025-06-18 11:05:23 +08:00
// console.log("图层关联验证结果:", isValidate);
// 排序
// 使用LayerSort工具重新排列画布对象如果可用
2026-01-05 11:47:36 +08:00
// 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
? 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?.();
// 确保所有对象的交互性正确设置
2026-01-02 11:24:11 +08:00
await this.layerManager?.updateLayersObjectsInteractivity?.();
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数据加载完成");
// 画布初始化事件
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);
}
}
2026-01-02 11:24:11 +08:00
/**
* 创建其他图层印花颜色元素...
* @param {Object} otherData - 其他图层数据
*/
async createOtherLayers(otherData) {
if (!otherData) return console.warn("otherData 为空不需要添加");
const otherData_ = JSON.parse(JSON.stringify(otherData));
console.log("==========创建其他图层", otherData_);
// 创建颜色图层
await this.createColorLayer(otherData_.color);
if(findLayer(this.layers.value, SpecialLayerId.SPECIAL_GROUP)){
console.warn("画布中已存在印花和元素组图层");
}else{
const printTrimsLayers = [];// 印花和元素图层
const singleLayers = [];// 平铺图层
otherData_?.printObject?.prints?.forEach((print, index) => {
print.name = t("Canvas.Print") + (index + 1);
if(print.ifSingle){
printTrimsLayers.unshift({...print});
}else{
singleLayers.unshift({...print});
}
})
2026-01-06 14:17:04 +08:00
otherData_?.trims?.prints?.forEach((trims, index) => {
trims.name = t("Canvas.Elements") + (index + 1);
printTrimsLayers.unshift({...trims});
2026-01-02 11:24:11 +08:00
})
await this.createPrintTrimsLayers(printTrimsLayers, singleLayers);
}
2026-01-05 11:47:36 +08:00
await this.changeCanvas();
2026-01-02 11:24:11 +08:00
}
2026-01-05 11:47:36 +08:00
// 设置画布对象的裁剪信息
async setObjecCliptInfo(tagObject, data){
const fixedLayerObj = this.getFixedLayerObject();
if(!fixedLayerObj) return console.warn("固定图层为空");
tagObject.set({
2026-01-02 11:24:11 +08:00
top: fixedLayerObj.top,
left: fixedLayerObj.left,
width: fixedLayerObj.width,
height: fixedLayerObj.height,
originX: fixedLayerObj.originX,
originY: fixedLayerObj.originY,
scaleX: fixedLayerObj.scaleX,
scaleY: fixedLayerObj.scaleY,
});
var object = fixedLayerObj;
const imageUrl = this.props.clothingImageUrl2;
if(imageUrl){
object = await new Promise((resolve, reject) => {
fabric.Image.fromURL(imageUrl, (imgObject) => {
2026-01-05 11:47:36 +08:00
tagObject.set({
2026-01-02 11:24:11 +08:00
width: imgObject.width,
height: imgObject.height,
});
resolve(imgObject);
}, { crossOrigin: "anonymous" });
});
}
2026-01-05 11:47:36 +08:00
const canvas = getObjectAlphaToCanvas(object, data);
2026-01-02 11:24:11 +08:00
const transparentMask = new fabric.Image(canvas, {
top: 0,
left: 0,
originX: fixedLayerObj.originX,
originY: fixedLayerObj.originY,
});
2026-01-05 11:47:36 +08:00
tagObject.set('clipPath', transparentMask);
2026-01-02 11:24:11 +08:00
}
async createColorLayer(color){
if(!color) return console.warn("颜色为空不需要添加");
if(findLayer(this.layers.value, SpecialLayerId.COLOR)) return console.warn("画布中已存在颜色图层");
console.log("==========添加颜色图层", color, this.layers.value.length)
// 创建颜色图层对象
const colorRect = new fabric.Rect({
id: SpecialLayerId.COLOR,
layerId: SpecialLayerId.COLOR,
layerName: t("Canvas.color"),
isVisible: true,
isLocked: true,
2026-01-05 11:47:36 +08:00
globalCompositeOperation: BlendMode.MULTIPLY,
2026-01-06 14:17:04 +08:00
originColor: color,
2026-01-02 11:24:11 +08:00
});
2026-01-06 14:17:04 +08:00
await this.setObjecCliptInfo(colorRect);
2026-01-02 11:24:11 +08:00
const gradientObj = palletToFill(color);
const gradient = new fabric.Gradient({
type: 'linear',
gradientUnits: 'percentage',
...gradientObj,
})
colorRect.set('fill', gradient);
this.canvas.add(colorRect);
// 创建颜色图层
const colorLayer = createLayer({
id: colorRect.layerId,
name: colorRect.layerName,
type: LayerType.SHAPE,
visible: colorRect.isVisible,
locked: colorRect.isLocked,
opacity: 1.0,
isFixedOther: true,
2026-01-05 11:47:36 +08:00
blendMode: BlendMode.MULTIPLY,
2026-01-02 11:24:11 +08:00
fabricObjects: [colorRect.toObject(["id", "layerId", "layerName"])],
})
const groupIndex = this.layers.value.findIndex(layer => layer.isFixed || layer.isBackground);
this.layers.value.splice(groupIndex, 0, colorLayer);
}
// 创建印花和元素图层
async createPrintTrimsLayers(printTrimsLayers, singleLayers){
console.log("==========添加印花和元素图层组", printTrimsLayers, singleLayers)
const fixedLayerObj = this.getFixedLayerObject();
const flWidth = fixedLayerObj.width
const flHeight = fixedLayerObj.height
const flTop = fixedLayerObj.top
const flLeft = fixedLayerObj.left
const flScaleX = fixedLayerObj.scaleX
const flScaleY = fixedLayerObj.scaleY
const children = [];
// 添加印花和元素图层
for(let index = 0; index < printTrimsLayers.length; index++){
2026-01-06 14:17:04 +08:00
let item = printTrimsLayers[index];
2026-01-02 11:24:11 +08:00
let id = generateId("layer_image_");
2026-01-06 14:17:04 +08:00
let name = item.name;
2026-01-02 11:24:11 +08:00
let image = await new Promise(resolve => {
2026-01-06 14:17:04 +08:00
fabric.Image.fromURL(item.path, (fabricImage)=>{
const left = flLeft - flWidth * flScaleX / 2 + (item.location?.[0] || 0) * flScaleX
const top = flTop - flHeight * flScaleY / 2 + (item.location?.[1] || 0) * flScaleY
const scaleX = flWidth * (item.scale?.[0] || 1) / fabricImage.width * flScaleX
const scaleY = flHeight * (item.scale?.[1] || 1) / fabricImage.height * flScaleY
2026-01-02 11:24:11 +08:00
const {x, y} = calculateRotatedTopLeftDeg(
fabricImage.width * scaleX,
fabricImage.height * scaleY,
left,
top,
0,
2026-01-06 14:17:04 +08:00
item.angle || 0
2026-01-02 11:24:11 +08:00
)
2026-01-06 14:17:04 +08:00
const angle = item.angle || 0
2026-01-02 11:24:11 +08:00
fabricImage.set({
left: x,
top: y,
scaleX: scaleX,
scaleY: scaleY,
angle: angle,
id: id,
layerId: id,
layerName: name,
selectable: true,
hasControls: true,
hasBorders: true,
2026-01-06 14:17:04 +08:00
sourceData: item,
2026-01-02 11:24:11 +08:00
});
resolve(fabricImage);
}, { crossOrigin: "anonymous" });
})
this.canvas.add(image);
let layer = createLayer({
id: id,
name: name,
type: LayerType.BITMAP,
visible: true,
locked: false,
opacity: 1.0,
fabricObjects: [image.toObject(["id", "layerId", "layerName"])],
})
children.push(layer);
};
// 添加平铺图层
for(let index = 0; index < singleLayers.length; index++){
2026-01-06 14:17:04 +08:00
let item = singleLayers[index];
2026-01-02 11:24:11 +08:00
let id = generateId("layer_image_");
2026-01-06 14:17:04 +08:00
let name = item.name;
2026-01-02 11:24:11 +08:00
let image = await new Promise(resolve => {
2026-01-06 14:17:04 +08:00
fabric.Image.fromURL(item.path, (fabricImage)=>{
2026-01-02 11:24:11 +08:00
const imgElement = fabricImage.getElement();
const tcanvas = document.createElement('canvas');
tcanvas.width = imgElement.width;
tcanvas.height = imgElement.height;
const ctx = tcanvas.getContext('2d');
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(imgElement, 0, 0);
resolve(tcanvas);
}, { crossOrigin: "anonymous" });
})
2026-01-06 14:17:04 +08:00
let scaleX = fixedLayerObj.width / image.width * (item.scale?.[0] || 1) / 5;
let scaleY = fixedLayerObj.height / image.height * (item.scale?.[1] || 1) / 5;
2026-01-02 11:24:11 +08:00
let scale = fixedLayerObj.width > fixedLayerObj.height ? scaleX : scaleY;
2026-01-06 14:17:04 +08:00
let left = (item.location?.[0] || 0) - image.width * scale / 2
let top = (item.location?.[1] || 0) - image.height * scale / 2
2026-01-02 11:24:11 +08:00
let rect = new fabric.Rect({
id: id,
layerId: id,
layerName: name,
width: fixedLayerObj.width,
height: fixedLayerObj.height,
top: fixedLayerObj.top,
left: fixedLayerObj.left,
scaleX: fixedLayerObj.scaleX,
scaleY: fixedLayerObj.scaleY,
originX: fixedLayerObj.originX,
originY: fixedLayerObj.originY,
2026-01-06 14:17:04 +08:00
sourceData: item,
2026-01-02 11:24:11 +08:00
fill: new fabric.Pattern({
source: image,
repeat: "repeat",
2026-01-06 14:17:04 +08:00
patternTransform: createPatternTransform(scale, item.angle || 0),
2026-01-02 11:24:11 +08:00
offsetX: left, // 水平偏移
offsetY: top, // 垂直偏移
}),
2026-01-06 14:17:04 +08:00
fill_ : {
source: item.path,
gapX: 0,
gapY: 0,
width: image.width,
height: image.height,
}
2026-01-02 11:24:11 +08:00
});
this.canvas.add(rect);
let layer = createLayer({
id: id,
name: name,
type: LayerType.BITMAP,
visible: true,
locked: true,
opacity: 1,
fabricObjects: [rect.toObject(["id", "layerId", "layerName"])],
})
children.push(layer);
};
if(children.length === 0){
let layer = createLayer({
id: generateId("layer_image_"),
name: t("Canvas.EmptyLayer"),
type: LayerType.BITMAP,
visible: true,
locked: false,
opacity: 1.0,
fabricObjects: [],
})
children.push(layer);
}
const groupRect = new fabric.Rect({});
2026-01-05 11:47:36 +08:00
await this.setObjecCliptInfo(groupRect);
2026-01-02 11:24:11 +08:00
// 插入组图层
const groupIndex = this.layers.value.findIndex(layer => layer.isFixedOther || layer.isFixed || layer.isBackground);
const groupLayer = createLayer({
id: SpecialLayerId.SPECIAL_GROUP,
name: t("Canvas.PrintAndElementsGroup"),
type: LayerType.GROUP,
visible: true,
locked: false,
opacity: 1.0,
fabricObjects: [],
children: children,
clippingMask: groupRect.toObject(),
isFixedClipMask: true,
});
this.layers.value.splice(groupIndex, 0, groupLayer);
}
2026-01-05 11:47:36 +08:00
/**
2026-01-06 14:17:04 +08:00
* 画布事件变更后
2026-01-05 11:47:36 +08:00
*/
async changeCanvas(){
2026-01-06 14:17:04 +08:00
// const fixedLayerObj = this.getFixedLayerObject();
// if(!fixedLayerObj) return console.warn("固定图层对象不存在", fixedLayerObj)
// const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR);
// if(colorObject){
// const ids = this.layerManager.getBlendModeLayerIds(SpecialLayerId.SPECIAL_GROUP);
// if(ids.length === 0){
// ids.unshift(SpecialLayerId.SPECIAL_GROUP);
// await this.setObjecCliptInfo(colorObject);
// this.canvas.renderAll();
// return;
// }
// const base64 = await this.exportManager.exportImage({layerIdArray2: ids, isEnhanceImg: true});
// if(!base64) return console.warn("导出图片失败", base64)
// const canvas = await base64ToCanvas(base64, fixedLayerObj.scaleX * 2, true);
// const ctx = canvas.getContext('2d');
// const width = fixedLayerObj.width;
// const height = fixedLayerObj.height;
// const x = (canvas.width - width) / 2;
// const y = (canvas.height - height) / 2;
// const data = ctx.getImageData(x, y, width, height);
// await this.setObjecCliptInfo(colorObject, data);
// this.canvas.renderAll();
// }
2026-01-05 11:47:36 +08:00
}
2025-06-09 10:25:54 +08:00
/**
* 缩放红绿图模式内容以适应当前画布大小
* 确保衣服底图和红绿图永远在画布内可见
* @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;
}
2026-01-02 11:24:11 +08:00
2025-06-09 10:25:54 +08:00
/**
* 获取所有普通图层对象包括红绿图
* @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;
}
2026-01-02 11:24:11 +08:00
/**
* 键盘移动激活对象
* @param {String} direction 移动方向up, down, left, right
* @param {<Number>} step 移动步长
* @private
*/
moveActiveObject(direction, step = 1) {
const objects = [];
const activeObject = this.canvas.getActiveObject();
if(!activeObject) return;
const initPos = {
id: activeObject.id,
left: activeObject.left,
top: activeObject.top,
};
switch(direction) {
case "up":
activeObject.top -= step;
break;
case "down":
activeObject.top += step;
break;
case "left":
activeObject.left -= step;
break;
case "right":
activeObject.left += step;
break;
}
if(!activeObject.id) return this.canvas.renderAll();
const cmd = new ObjectMoveCommand({
canvas: this.canvas,
initPos,
finalPos: {
id: activeObject.id,
left: activeObject.left,
top: activeObject.top,
},
});
this.commandManager.executeCommand(cmd);
}
2025-06-09 10:25:54 +08:00
}