Files
aida_front/src/component/Canvas/CanvasEditor/managers/CanvasManager.js
2026-01-16 10:29:03 +08:00

1995 lines
62 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { fabric } from "fabric-with-all";
import initAligningGuidelines, {
initCenteringGuidelines,
} from "../utils/helperLine";
import { ThumbnailManager } from "./ThumbnailManager";
import { ExportManager } from "./ExportManager";
import {
findLayerRecursively,
isGroupLayer,
OperationType,
OperationTypes,
findLayer,
createLayer,
LayerType,
SpecialLayerId,
BlendMode,
} from "../utils/layerHelper";
import { ObjectMoveCommand } from "../commands/ObjectCommands";
import { AnimationManager } from "./animation/AnimationManager";
import { createCanvas } from "../utils/canvasFactory";
import { CanvasEventManager } from "./events/CanvasEventManager";
import CanvasConfig from "../config/canvasConfig";
import { RedGreenModeManager } from "./RedGreenModeManager";
import { EraserStateManager } from "./EraserStateManager";
import {
deepClone,
findObjectById,
generateId,
optimizeCanvasRendering,
palletToFill,
fillToCssStyle,
calculateRotatedTopLeftDeg,
calculateCenterPoint,
createPatternTransform,
getTransformScaleAngle,
base64ToCanvas,
imageAddGapToCanvas,
} from "../utils/helper";
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
import { isFunction } from "lodash-es";
import {
restoreObjectLayerAssociations,
simplifyLayers,
validateLayerAssociations,
} from "../utils/layerUtils";
import { imageModeHandler } from "../utils/imageHelper";
import { getObjectAlphaToCanvas } from "../utils/objectHelper";
import { AddLayerCommand, RemoveLayerCommand, ToggleChildLayerVisibilityCommand } from "../commands/LayerCommands";
import { fa, id } from "element-plus/es/locales.mjs";
import i18n from "@/lang/index.ts";
const {t} = i18n.global;
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
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; // 是否允许擦除固定图层
this.eraserStateManager = null; // 橡皮擦状态管理器引用
this.handleCanvasInit = null; // 画布初始化回调函数
this.props = options.props || {};
this.emit = options.emit || (() => {});
// 初始化画布
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, {
// 可以根据需求自定义选项
// layerThumbSize: { width: 32, height: 32 },
// elementThumbSize: { width: 32, height: 24 },
layers: this.layers,
});
this.canvas.thumbnailManager = this.thumbnailManager; // 将缩略图管理器绑定到画布
// 设置画布辅助线
initAligningGuidelines(this.canvas);
// 设置画布中心线
// initCenteringGuidelines(this.canvas);
// 初始化画布事件监听器
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_"),
});
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("没有活动图层,无法添加图像");
}
}
}
/**
* 初始化画布事件监听器
* 设置鼠标事件、键盘事件等
* @private
*/
_initCanvasEvents() {
// 添加笔刷图像转换处理回调
this.canvas.onBrushImageConverted = async (fabricImage) => {
await this.addImageToLayer({ fabricImage, targetLayerId: null });
// 返回false表示使用默认行为直接添加到画布
return false;
};
this.eraserStateManager = new EraserStateManager(
this.canvas,
this.layerManager
);
// 监听擦除开始事件
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?.(); // 如果没有命令管理器,直接执行命令
}
// 更新交互性
command &&
(await this.layerManager?.updateLayersObjectsInteractivity?.());
this.thumbnailManager?.generateLayerThumbnail(
this.layerManager?.activeLayerId?.value
);
// 固定图层 的擦除也需要重新生成缩略图 要判断 当前固定图层是否锁定
const fixedLayer = this.layers?.value?.find(
(layer) => layer.isFixed && !layer.locked
);
// 如果有固定图层且未锁定,则生成缩略图
fixedLayer &&
this.isFixedErasable &&
this.thumbnailManager?.generateLayerThumbnail(fixedLayer?.id);
});
}
/**
* 设置编辑器模式
* @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;
}
if (this.eraserStateManager) {
this.eraserStateManager.setLayerManager(this.layerManager);
}
}
/**
* 设置命令管理器
* @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;
}
}
/**
* 设置部件选择管理器
* @param {Object} partManager 部件选择管理器实例
*/
setPartManager(partManager) {
this.partManager = partManager;
// 如果已创建事件管理器,更新它的部件选择管理器引用
if (this.eventManager) {
this.eventManager.partManager = this.partManager;
}
}
// 设置红绿图模式管理器
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,
});
// 设置动画交互效果
this.animationManager.setupInteractionAnimations();
}
setupCanvasInitEvent(handleCanvasInit) {
this.handleCanvasInit = handleCanvasInit;
}
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属性
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) {
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();
}
// 居中所有画布元素,包括背景层和其他元素
this.centerAllObjects();
// // 重新渲染画布使变更生效
// this.canvas.renderAll();
}
/**
* 重置视图变换,使元素回到原始位置
* @private
*/
_resetViewportTransform(zoom) {
// 保存当前缩放值
const currentZoom = zoom ?? this.canvas.getZoom();
// 重置视图变换,但保留缩放级别
this.canvas.setViewportTransform([currentZoom, 0, 0, currentZoom, 0, 0]);
}
/**
* 居中所有画布元素
* 以背景层为参照,计算背景层的偏移量并应用到所有对象上
* 这样可以保持对象间的相对位置关系不变
*/
async 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
);
// 如果没有可见对象,直接返回
if (visibleObjects.length === 0) return;
// 获取背景对象
const backgroundObject = visibleObjects.find((obj) => obj.isBackground);
// !this.canvas?.clipPath &&
// this.centerBackgroundLayer(this.canvas.width, this.canvas.height);
this.canvas?.clipPath?.set?.({
left: this.width / 2,
top: this.height / 2,
originX: "center",
originY: "center",
});
this.canvas?.clipPath?.setCoords?.();
// 如果只有背景层或没有背景层,使用原有逻辑
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();
}
return;
}
// 记录背景层居中前的位置
const backgroundOldLeft = backgroundObject.left;
const backgroundOldTop = backgroundObject.top;
// 计算画布中心点
const canvasCenterX = this.width / 2;
const canvasCenterY = this.height / 2;
// 设置背景层居中
backgroundObject.set({
left: canvasCenterX,
top: canvasCenterY,
originX: "center",
originY: "center",
});
// 计算背景层的偏移量
const deltaX = backgroundObject.left - backgroundOldLeft;
const deltaY = backgroundObject.top - backgroundOldTop;
// 将相同的偏移量应用到所有其他对象上
const otherObjects = visibleObjects.filter(
(obj) => obj !== backgroundObject
);
otherObjects.forEach((obj) => {
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;
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) {
setTimeout(() => {
this.layerManager?.updateLayersObjectsInteractivity?.(false, {
isMoveing: true,
});
});
}
// 如果有背景层,更新蒙层位置
if (backgroundObject && CanvasConfig.isCropBackground) {
this.updateMaskPosition(backgroundObject);
}
// 更新颜色层信息
// const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR);
// if(colorObject){
// await this.setObjecCliptInfo(colorObject);
// }
const groupLayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP);
if(groupLayer){
const groupRect = new fabric.Rect({});
await this.setObjecCliptInfo(groupRect);
groupLayer.clippingMask = groupRect.toObject();
}
// 重新渲染画布
this.canvas.renderAll();
}
/**
* 计算多个对象的总边界框
* @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;
// this.canvas.setBackgroundColor(
// color,
// this.canvas.renderAll.bind(this.canvas)
// );
this.thumbnailManager?.generateLayerThumbnail?.(
this.layers?.value.find((layer) => layer.isBackground)?.id
);
}
/**
* 居中背景层
* @param {Object} backgroundLayerObject 背景层对象
* @param {Number} canvasWidth 画布宽度
* @param {Number} canvasHeight 画布高度
*/
async 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 &&
!this.enabledRedGreenMode &&
this.createOrUpdateMask(backgroundLayerObject);
return true;
}
/**
* 创建或更新蒙层,用于裁剪不可见区域
* @param {Object} backgroundLayerObject 背景层对象
*/
createOrUpdateMask(backgroundLayerObject) {
if (!backgroundLayerObject) return;
const bgWidth = backgroundLayerObject.width * backgroundLayerObject.scaleX;
const bgHeight =
backgroundLayerObject.height * backgroundLayerObject.scaleY;
const left = backgroundLayerObject.left;
const top = backgroundLayerObject.top;
// 如果已经存在蒙层,则更新它
if (this.maskLayer) {
this.canvas.remove(this.maskLayer);
}
this.canvas.getObjects().forEach((obj) => {
if (obj.id === "canvasMaskLayer") {
this.canvas.remove(obj);
}
})
// 创建蒙层 - 使用透明矩形作为裁剪区域
this.maskLayer = new fabric.Rect({
id: "canvasMaskLayer",
width: bgWidth,
height: bgHeight,
left: left,
top: top,
fill: "transparent",
stroke: "transparent",
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,
left: left,
top: top,
originX: backgroundLayerObject.originX || "left",
originY: backgroundLayerObject.originY || "top",
absolutePositioned: true,
rx: 15,
ry: 15,
});
}
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) {
console.warn(
"CanvasManager.js = >getBackgroundLayer 方法没有找到背景层"
);
}
return backgroundLayerByBgLayer;
}
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;
}
getObjectsByIds(ids){
const objects = this.canvas.getObjects().filter((obj) => {
return ids.includes(obj.id);
});
return objects;
}
/**
* 更新蒙层位置
* @param {Object} backgroundLayerObject 背景层对象
*/
updateMaskPosition(backgroundLayerObject) {
if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath)
return;
console.log("backgroundLayerObject");
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) {
this.thumbnailManager?.generateLayerThumbnail?.(layerId);
}
/**
* 更新所有图层和元素的缩略图
*/
updateAllThumbnails() {
// 为所有元素生成缩略图
this.thumbnailManager?.generateAllLayerThumbnails?.(this.layers.value);
}
/**
*
* 更改固定图层的图片
* @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;
}
const command = new ChangeFixedImageCommand({
canvas: this.canvas,
layerManager: this.layerManager,
imageUrl: imageUrl,
targetLayerType: options.targetLayerType || "fixed", // background/fixed
canvasWidth: this.canvasWidth,
canvasHeight: this.canvasHeight,
...options,
});
command.undoable =
options.undoable !== undefined ? options.undoable : false; // 默认不可撤销 undoable = true 为可撤销
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;
}
if (options.targetLayerType === "background") {
return fItem.isBackground;
}
return false;
});
// 如果找到了图层,则生成缩略图
findLayer && this.thumbnailManager?.generateLayerThumbnail(findLayer.id);
this.layerManager?.sortLayers?.();
return result;
}
/**
* 导出图片
* @param {Object} options 导出选项
* @param {Boolean} options.isContainBg 是否包含背景图层
* @param {Boolean} options.isContainFixed 是否包含固定图层
* @param {Boolean} options.isContainFixedOther 是否包含其他固定图层
* @param {Boolean} options.isPrintTrimsNoRepeat 是否包含印花图层的不平铺
* @param {Boolean} options.isPrintTrimsRepeat 是否包含印花图层的平铺
* @param {Boolean} options.isContainNormalLayer 是否包含普通图层
* @param {String} options.layerId 导出具体图层ID
* @param {Array} options.layerIdArray 导出多个图层ID数组
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
* @param {Boolean} options.restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
* @param {Boolean} options.isEnhanceImg 是否是增强图片
* @param {Boolean} options.isCropByBg 是否使用背景大小裁剪
* @returns {String} 导出的图片数据URL
*/
async exportImage(options = {}) {
if (!this.exportManager) {
console.error("导出管理器未初始化,请确保已设置图层管理器");
throw new Error("导出管理器未初始化");
}
try {
// 如果当前有选中对象,先清除选中状态 否则导出有问题
// this.canvas.discardActiveObject(); // 清除选中状态
// this.canvas.renderAll(); // 重新渲染画布
// 自动设置红绿图模式相关参数
const enhancedOptions = {
isPrintTrimsNoRepeat: true,
isPrintTrimsRepeat: true,
isContainNormalLayer: true,
...options,
// 如果没有明确指定,则根据当前模式自动设置
restoreOpacityInRedGreen:
options.restoreOpacityInRedGreen !== undefined
? options.restoreOpacityInRedGreen
: false, // 默认在红绿图模式下恢复透明度
// excludedLayers: [SpecialLayerId.SPECIAL_GROUP], // 导出时排除的图层ID数组
};
// 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层
if (
this.enabledRedGreenMode &&
!options.layerId &&
(!options.layerIdArray || options.layerIdArray.length === 0)
) {
console.log("检测到红绿图模式,自动包含所有普通图层进行导出");
// 获取所有非背景、非固定的普通图层ID
const normalLayerIds =
this.layers?.value
?.filter(
(layer) => !layer.isBackground && !layer.isFixed && !layer.isFixedOther && layer.visible
)
?.map((layer) => layer.id) || [];
if (normalLayerIds.length > 0) {
enhancedOptions.layerIdArray = normalLayerIds;
console.log("红绿图模式导出图层:", normalLayerIds);
}
}
// 处理特殊图层的显示状态
const ptlids = [];
if(!enhancedOptions.isPrintTrimsNoRepeat || !enhancedOptions.isPrintTrimsRepeat){
let layers = this.layers?.value?.find((layer) => layer.isPrintTrimsGroup)?.children || [];
for(let layer of layers){
if(!layer.visible) continue;
let repeat = layer.fabricObjects?.[0]?.fill?.repeat || "no-repeat";
if(typeof repeat !== "string") repeat = "no-repeat";
if(repeat === "no-repeat"){
if(enhancedOptions.isPrintTrimsNoRepeat) continue;
}else{
if(enhancedOptions.isPrintTrimsRepeat) continue;
}
ptlids.push(layer.id);
const command = new ToggleChildLayerVisibilityCommand({
canvas: this.canvas,
layers: this.layers,
layerId: layer.id,
layerManager: this.layerManager,
});
await command.execute(false);
}
await this.changeCanvas();
}
const res = await this.exportManager.exportImage(enhancedOptions);
// 恢复特殊图层的显示状态
if(ptlids.length > 0){
for(let id of ptlids){
const command = new ToggleChildLayerVisibilityCommand({
canvas: this.canvas,
layers: this.layers,
layerId: id,
layerManager: this.layerManager,
});
await command.execute(true);
}
await this.changeCanvas();
}
return res;
} catch (error) {
console.warn("CanvasManager导出图片失败:", error);
throw error;
}
}
/**
* 导出印花元素颜色信息
* @returns {Object}
*/
async exportExtraInfo() {
// 导出颜色图层信息
const color = await this.exportColorLayer().catch(() => (null));
// 导出印花和元素图层信息
const printTrimsData = await this.exportPrintTrimsLayers().catch(() => ({prints: null, trims: null}));
return {
color,
...printTrimsData,
};
}
/**
* 导出颜色图层
* @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("颜色图层不存在");
}
const css = fillToCssStyle(object.fill)
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();
const color = object.originColor;
return {css, base64, color};
}
/**
* 导出印花和元素图层
*/
async exportPrintTrimsLayers() {
const glayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP);
if(!glayer) return Promise.reject("印花和元素图层组不存在");
const ids = glayer.children.map((v) => v.id);
const objects = this.getObjectsByIds(ids);
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 sourceData = glayer.children.find((v_) => v_.id === v.id)?.metadata?.sourceData;
if(!sourceData) return;
const obj = {
ifSingle: typeof v.fill === "string",
level2Type: sourceData.level2Type,
designType: sourceData.designType,
path: sourceData.path,
minIOPath: sourceData.minIOPath,
location: [0, 0],
scale: [0, 0],
angle: v.angle,
name: sourceData.name,
priority: sourceData.priority,
object:{
top: 0,
left: 0,
scaleX: 0,//对象的缩放比例
scaleY: 0,//对象的缩放比例
opacity: v.opacity,
angle: v.angle,
flipX: v.flipX,//是否水平翻转
flipY: v.flipY,//是否垂直翻转
blendMode: v.globalCompositeOperation,// 混合模式
gapX: 0,// 平铺模式下的间距
gapY: 0,// 平铺模式下的间距
}
}
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 oX = (cx-width/2) / flScaleX;
let oY = (cy-height/2) / flScaleY;
let oScaleX = (v.width * v.scaleX) / (flWidth * flScaleX);
let oScaleY = (v.height * v.scaleY) / (flHeight * flScaleY);
// obj.object.width = width;
// obj.object.height = height;
obj.object.top = oY;
obj.object.left = oX;
obj.object.scaleX = oScaleX;
obj.object.scaleY = oScaleY;
if(obj.ifSingle){
obj.location = [oX, oY];
obj.scale = [oScaleX, oScaleY];
}else{
let fill = v.fill;
let fill_ = v.fill_;
if(!fill || !fill_) return console.warn("印花元素不存在fill或fill_属性");
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];
obj.gap = [fill_.gapX, fill_.gapY];
}
if(obj.level2Type === "Pattern"){
prints.push(obj);
}else if(obj.level2Type === "Embroidery"){
trims.push(obj);
}
})
// prints.sort((a, b) => a.ifSingle ? 1 : -1);
prints.forEach((v, i) => v.priority = i + 1);
trims.forEach((v, i) => v.priority = i + 1);
return {prints, trims};
}
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() {
try {
// 清除画布中选中状态
// this.canvas.discardActiveObject();
this.canvas.renderAll();
// 排除颜色图层和特殊组图层
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));
const simplifyLayersData = simplifyLayers(
JSON.parse(JSON.stringify(this.layers.value)),
excludedLayers
);
const data = {
canvas,
layers: simplifyLayersData, // 简化图层数据
version: "1.0", // 添加版本信息
timestamp: new Date().toISOString(), // 添加时间戳
canvasWidth: this.canvasWidth.value,
canvasHeight: this.canvasHeight.value,
canvasColor: this.canvasColor.value,
activeLayerId: this.layerManager?.activeLayerId?.value,
};
console.log("获取画布JSON数据...", data);
return JSON.stringify(data);
} catch (error) {
console.error("获取画布JSON失败:", error);
throw new Error("获取画布JSON失败");
}
}
loadJSON(json, calllBack) {
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;
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
const tempLayers = parsedJson?.layers || [];
const canvasData = parsedJson?.canvas;
if (!tempLayers) {
reject(new Error("JSON数据中缺少layers字段"));
return;
}
if (!canvasData) {
reject(new Error("JSON数据中缺少canvas字段"));
return;
}
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;
console.log("是否检测到红绿图模式内容:", this.enabledRedGreenMode);
// 重置视图变换以确保元素位置正确
this._resetViewportTransform(1);
// let canvasClipPath = null;
// // 克隆当前裁剪路径
// if (this.canvas?.clipPath) {
// canvasClipPath = this.canvas?.clipPath;
// }
// 清除当前画布内容
// this.canvas.clear(); // 清除画布内容 可以先去掉 这样加载闪动的情况就比较少 如果有问题 可以再打开
console.log("清除当前画布内容", canvasData);
delete canvasData.clipPath; // 删除当前裁剪路径
// 加载画布数据
this.canvas.loadFromJSON(canvasData, async () => {
await optimizeCanvasRendering(this.canvas, async () => {
// 清空重做栈
this.commandManager?.clear?.();
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;
// }
try {
// 重置画布数据
await this.setCanvasSize(this.canvas.width, this.canvas.height);
await this.centerBackgroundLayer(this.canvas.width, this.canvas.height);
await this.createOtherLayers(this.props.otherData);
// 重新构建对象关系
// restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects());
// 验证图层关联关系 - 稳定后可以注释
// const isValidate = validateLayerAssociations(
// this.layers.value,
// this.canvas.getObjects()
// );
// console.log("图层关联验证结果:", isValidate);
// 排序
// 使用LayerSort工具重新排列画布对象如果可用
await this?.layerManager?.layerSort?.rearrangeObjects();
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;
// // 如果检测到红绿图模式内容,进行缩放调整
// if (this.enabledRedGreenMode) {
// this._rescaleRedGreenModeContent();
// }
// 重载代码后支持回调中操作一些内容
// 确保所有对象的交互性正确设置
await this.layerManager?.updateLayersObjectsInteractivity?.();
await calllBack?.();
this.emit("canvas-load-json-success");
// 更新所有缩略图
setTimeout(() => {
this.updateAllThumbnails();
}, 500);
console.log("画布JSON数据加载完成");
// 画布初始化事件
this.handleCanvasInit?.(true);
resolve();
} catch (error) {
console.error("恢复图层数据失败:", error);
reject(new Error("恢复图层数据失败: " + error.message));
}
});
});
});
} catch (error) {
console.error("解析JSON失败:", error);
throw new Error("解析JSON失败请检查输入格式: " + error.message);
}
}
/**
* 创建其他图层:印花、颜色、元素...
* @param {Object} otherData - 其他图层数据
*/
async createOtherLayers(otherData, isUpdate = false) {
if (!otherData) return console.warn("otherData 为空不需要添加");
const otherData_ = JSON.parse(JSON.stringify(otherData));
console.log("==========创建其他图层", otherData_);
const updateColor = !!otherData_.color;
const updateSpecialGroup = !!otherData_.printObject || !!otherData_.trims;
// 删除颜色图层和特殊组图层
const ids = [];
if(isUpdate){
updateColor && ids.push(SpecialLayerId.COLOR)
updateSpecialGroup && ids.push(SpecialLayerId.SPECIAL_GROUP)
}else{
ids.push(SpecialLayerId.COLOR)
ids.push(SpecialLayerId.SPECIAL_GROUP)
}
this.layers.value = this.layers.value.filter((layer) => {
if(ids.includes(layer.id)){
ids.push(...layer.children?.map((child) => child.id));
return false;
}
return true;
})
this.canvas.getObjects().forEach((v) => ids.includes(v.id) && this.canvas.remove(v))
// 创建颜色图层
otherData_.color && await this.createColorLayer(otherData_.color);
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});
}
})
otherData_.trims?.prints?.forEach((trims, index) => {
trims.name = t("Canvas.Elements") + (index + 1);
printTrimsLayers.unshift({...trims});
})
if(isUpdate ? updateSpecialGroup : true){
await this.createPrintTrimsLayers(printTrimsLayers, singleLayers);
}
await this.changeCanvas();
}
// 设置画布对象的裁剪信息
async setObjecCliptInfo(tagObject, data){
const fixedLayerObj = this.getFixedLayerObject();
if(!fixedLayerObj) return console.warn("固定图层为空");
tagObject.set({
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) => {
tagObject.set({
width: imgObject.width,
height: imgObject.height,
});
resolve(imgObject);
}, { crossOrigin: "anonymous" });
});
}
const canvas = getObjectAlphaToCanvas(object, data);
const transparentMask = new fabric.Image(canvas, {
top: 0,
left: 0,
originX: fixedLayerObj.originX,
originY: fixedLayerObj.originY,
});
tagObject.set('clipPath', transparentMask);
}
async createColorLayer(color_){
const color = color_ || {r:0,g:0,b:0,a:0};
// 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,
selectable: false,
hasControls: false,
hasBorders: false,
globalCompositeOperation: BlendMode.MULTIPLY,
originColor: color,
});
// await this.setObjecCliptInfo(colorRect);
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,
blendMode: BlendMode.MULTIPLY,
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){
// if(findLayer(this.layers.value, SpecialLayerId.SPECIAL_GROUP)) {
// return console.warn("画布中已存在印花和元素组图层");
// }
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++){
let item = printTrimsLayers[index];
let id = generateId("layer_image_");
let name = item.name;
let image = await new Promise(resolve => {
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
const {x, y} = calculateRotatedTopLeftDeg(
fabricImage.width * scaleX,
fabricImage.height * scaleY,
left,
top,
0,
item.angle || 0
)
const angle = item.angle || 0
fabricImage.set({
left: x,
top: y,
scaleX: scaleX,
scaleY: scaleY,
angle: angle,
id: id,
layerId: id,
layerName: name,
selectable: true,
hasControls: true,
hasBorders: true,
isPrintTrims: true,
globalCompositeOperation: BlendMode.MULTIPLY,
});
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,
isPrintTrims: true,
blendMode: BlendMode.MULTIPLY,
fabricObjects: [image.toObject(["id", "layerId", "layerName"])],
metadata: {sourceData: item},
})
children.push(layer);
};
// 添加平铺图层
for(let index = 0; index < singleLayers.length; index++){
let item = singleLayers[index];
let id = generateId("layer_image_");
let name = item.name;
let image = await new Promise(resolve => {
fabric.Image.fromURL(item.path, (fabricImage)=>{
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" });
})
let scaleX_ = fixedLayerObj.width / image.width * (item.scale?.[0] || 1) / 5;
let scaleY_ = fixedLayerObj.height / image.height * (item.scale?.[1] || 1) / 5;
let scale = fixedLayerObj.width > fixedLayerObj.height ? scaleX_ : scaleY_;
let offsetX = (item.location?.[0] || 0) - image.width * scale / 2
let offsetY = (item.location?.[1] || 0) - image.height * scale / 2
let top = fixedLayerObj.top - fixedLayerObj.height * fixedLayerObj.scaleY / 2
let left = fixedLayerObj.left - fixedLayerObj.width * fixedLayerObj.scaleX / 2
let scaleX = fixedLayerObj.scaleX
let scaleY = fixedLayerObj.scaleY
let opacity = 1
let angle = 0
let gapX = 0
let gapY = 0
let fillSource = image
if(item.object){
top += item.object.top * fixedLayerObj.scaleY
left += item.object.left * fixedLayerObj.scaleX
scaleX *= item.object.scaleX
scaleY *= item.object.scaleY
opacity = item.object.opacity
angle = item.object.angle
gapX = item.object.gapX
gapY = item.object.gapY
fillSource = imageAddGapToCanvas(image, gapX, gapY);
}
let rect = new fabric.Rect({
id: id,
layerId: id,
layerName: name,
width: fixedLayerObj.width,
height: fixedLayerObj.height,
top: top,
left: left,
scaleX: scaleX,
scaleY: scaleY,
globalCompositeOperation: BlendMode.MULTIPLY,
fill: new fabric.Pattern({
source: fillSource,
repeat: "repeat",
patternTransform: createPatternTransform(scale, item.angle || 0),
offsetX: offsetX, // 水平偏移
offsetY: offsetY, // 垂直偏移
}),
fill_ : {
source: item.path,
gapX: gapX,
gapY: gapY,
width: image.width,
height: image.height,
},
isPrintTrims: true,
});
this.canvas.add(rect);
let layer = createLayer({
id: id,
name: name,
type: LayerType.BITMAP,
visible: true,
locked: true,
opacity: 1,
isPrintTrims: true,
blendMode: BlendMode.MULTIPLY,
fabricObjects: [rect.toObject(["id", "layerId", "layerName"])],
metadata: {sourceData: item},
})
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);
// }
if(children.length === 0) return;
const groupRect = new fabric.Rect({});
await this.setObjecCliptInfo(groupRect);
// 插入组图层
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(),
isPrintTrimsGroup: true,
});
this.layers.value.splice(groupIndex, 0, groupLayer);
}
/**
* 画布事件变更后
*/
async changeCanvas(){
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();
}
}
/**
* 缩放红绿图模式内容以适应当前画布大小
* 确保衣服底图和红绿图永远在画布内可见
* @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 [];
// 查找所有非背景、非固定的普通图层
const normalLayers = this.layers.value.filter(
(layer) => !layer.isBackground && !layer.isFixed
);
// 收集所有普通图层中的对象
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 =
Math.abs(
obj.width * obj.scaleX -
fixedLayerObject.width * fixedLayerObject.scaleX
) <
fixedLayerObject.width * fixedLayerObject.scaleX * 0.05 &&
Math.abs(
obj.height * obj.scaleY -
fixedLayerObject.height * fixedLayerObject.scaleY
) <
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;
}
/**
* 键盘移动激活对象
* @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);
}
}