画布增加的新功能

This commit is contained in:
李志鹏
2026-01-02 11:24:11 +08:00
parent 1ae365b1f3
commit f8e4ab8cdb
59 changed files with 4401 additions and 1213 deletions

View File

@@ -9,7 +9,12 @@ import {
isGroupLayer,
OperationType,
OperationTypes,
findLayer,
createLayer,
LayerType,
SpecialLayerId,
} from "../utils/layerHelper";
import { ObjectMoveCommand } from "../commands/ObjectCommands";
import { AnimationManager } from "./animation/AnimationManager";
import { createCanvas } from "../utils/canvasFactory";
import { CanvasEventManager } from "./events/CanvasEventManager";
@@ -21,6 +26,10 @@ import {
findObjectById,
generateId,
optimizeCanvasRendering,
palletToFill,
fillToCssStyle,
calculateRotatedTopLeftDeg,
createPatternTransform,
} from "../utils/helper";
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
import { isFunction } from "lodash-es";
@@ -30,6 +39,11 @@ import {
validateLayerAssociations,
} from "../utils/layerUtils";
import { imageModeHandler } from "../utils/imageHelper";
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;
export class CanvasManager {
constructor(canvasElement, options) {
@@ -50,6 +64,7 @@ export class CanvasManager {
this.isFixedErasable = options.isFixedErasable || false; // 是否允许擦除固定图层
this.eraserStateManager = null; // 橡皮擦状态管理器引用
this.handleCanvasInit = null; // 画布初始化回调函数
this.props = options.props || {};
// 初始化画布
this.initializeCanvas();
}
@@ -83,10 +98,10 @@ export class CanvasManager {
this.canvas.thumbnailManager = this.thumbnailManager; // 将缩略图管理器绑定到画布
// // 设置画布辅助线
// initAligningGuidelines(this.canvas);
// 设置画布辅助线
initAligningGuidelines(this.canvas);
// // 设置画布中心线
// 设置画布中心线
// initCenteringGuidelines(this.canvas);
// 初始化画布事件监听器
@@ -431,7 +446,7 @@ export class CanvasManager {
* 以背景层为参照,计算背景层的偏移量并应用到所有对象上
* 这样可以保持对象间的相对位置关系不变
*/
centerAllObjects() {
async centerAllObjects() {
if (!this.canvas) return;
// 获取所有可见对象(不是背景元素的对象)
@@ -448,8 +463,8 @@ export class CanvasManager {
// 获取背景对象
const backgroundObject = visibleObjects.find((obj) => obj.isBackground);
!this.canvas?.clipPath &&
this.centerBackgroundLayer(this.canvas.width, this.canvas.height);
// !this.canvas?.clipPath &&
// this.centerBackgroundLayer(this.canvas.width, this.canvas.height);
this.canvas?.clipPath?.set?.({
left: this.width / 2,
@@ -496,7 +511,6 @@ export class CanvasManager {
// 计算背景层的偏移量
const deltaX = backgroundObject.left - backgroundOldLeft;
const deltaY = backgroundObject.top - backgroundOldTop;
// 将相同的偏移量应用到所有其他对象上
const otherObjects = visibleObjects.filter(
(obj) => obj !== backgroundObject
@@ -549,8 +563,21 @@ export class CanvasManager {
this.updateMaskPosition(backgroundObject);
}
// 更新颜色层信息
const fixedLayerObj = this.getFixedLayerObject();
const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR);
if(colorObject && fixedLayerObj){
await this.setColorObjectInfo(colorObject, fixedLayerObj);
}
const groupLayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP);
if(groupLayer && fixedLayerObj){
const groupRect = new fabric.Rect({});
await this.setColorObjectInfo(groupRect, fixedLayerObj);
groupLayer.clippingMask = groupRect.toObject();
}
// 重新渲染画布
// this.canvas.renderAll();
this.canvas.renderAll();
}
/**
@@ -600,7 +627,7 @@ export class CanvasManager {
* @param {Number} canvasWidth 画布宽度
* @param {Number} canvasHeight 画布高度
*/
centerBackgroundLayer(canvasWidth, canvasHeight) {
async centerBackgroundLayer(canvasWidth, canvasHeight) {
const backgroundLayerObject = this.getBackgroundLayer();
if (!backgroundLayerObject) return false;
@@ -646,6 +673,11 @@ export class CanvasManager {
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({
@@ -706,6 +738,75 @@ export class CanvasManager {
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;
}
/**
* 更新蒙层位置
* @param {Object} backgroundLayerObject 背景层对象
@@ -798,7 +899,7 @@ export class CanvasManager {
// 如果找到了图层,则生成缩略图
findLayer && this.thumbnailManager?.generateLayerThumbnail(findLayer.id);
this.layerManager?.sortLayers?.();
return result;
}
@@ -812,6 +913,7 @@ export class CanvasManager {
* @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 = {}) {
@@ -857,11 +959,55 @@ export class CanvasManager {
}
return await this.exportManager.exportImage(enhancedOptions);
} catch (error) {
console.error("CanvasManager导出图片失败:", error);
console.warn("CanvasManager导出图片失败:", error);
throw error;
}
}
/**
* 导出颜色图层
* @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 color = fillToCssStyle(object.fill)
const canvas = new fabric.StaticCanvas();
canvas.setDimensions({
width: object.width,
height: object.height,
backgroundColor: null,
// enableRetinaScaling: true,
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();
return {color, base64};
}
dispose() {
// 释放导出管理器资源
if (this.exportManager) {
@@ -956,14 +1102,13 @@ export class CanvasManager {
// };
try {
// 清除画布中选中状态
this.canvas.discardActiveObject();
// this.canvas.discardActiveObject();
this.canvas.renderAll();
const simplifyLayersData = simplifyLayers(
JSON.parse(JSON.stringify(this.layers.value))
);
console.log("获取画布JSON数据...", simplifyLayersData);
return JSON.stringify({
const data = JSON.stringify({
canvas: this.canvas.toJSON([
"id",
"type",
@@ -978,6 +1123,7 @@ export class CanvasManager {
"eraserable",
"erasable",
"customType",
"fill_",
]),
layers: simplifyLayersData, // 简化图层数据
// layers: JSON.stringify(JSON.parse(JSON.stringify(this.layers.value))), // 全数据
@@ -988,6 +1134,8 @@ export class CanvasManager {
canvasColor: this.canvasColor.value,
activeLayerId: this.layerManager?.activeLayerId?.value,
});
console.log("获取画布JSON数据...", data);
return data;
} catch (error) {
console.error("获取画布JSON失败:", error);
throw new Error("获取画布JSON失败");
@@ -1070,8 +1218,10 @@ export class CanvasManager {
// }
try {
// 重置画布数据
this.setCanvasSize(this.canvas.width, this.canvas.height);
this.centerBackgroundLayer(this.canvas.width, this.canvas.height);
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());
// 验证图层关联关系 - 稳定后可以注释
@@ -1099,9 +1249,7 @@ export class CanvasManager {
await calllBack?.();
// 确保所有对象的交互性正确设置
await this.layerManager?.updateLayersObjectsInteractivity?.(
false
);
await this.layerManager?.updateLayersObjectsInteractivity?.();
console.log(this.layerManager.layers.value);
// 更新所有缩略图
@@ -1126,6 +1274,253 @@ export class CanvasManager {
}
}
/**
* 创建其他图层:印花、颜色、元素...
* @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});
}
})
otherData_?.trims?.prints?.forEach((print, index) => {
print.name = t("Canvas.Elements") + (index + 1);
printTrimsLayers.unshift({...print});
})
await this.createPrintTrimsLayers(printTrimsLayers, singleLayers);
}
}
async setColorObjectInfo(colorRect, fixedLayerObj){
colorRect.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) => {
colorRect.set({
width: imgObject.width,
height: imgObject.height,
});
resolve(imgObject);
}, { crossOrigin: "anonymous" });
});
}
const canvas = getObjectAlphaToCanvas(object);
const transparentMask = new fabric.Image(canvas, {
top: 0,
left: 0,
originX: fixedLayerObj.originX,
originY: fixedLayerObj.originY,
});
colorRect.set('clipPath', transparentMask);
}
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 fixedLayerObj = this.getFixedLayerObject();
// 创建颜色图层对象
const colorRect = new fabric.Rect({
id: SpecialLayerId.COLOR,
layerId: SpecialLayerId.COLOR,
layerName: t("Canvas.color"),
isVisible: true,
isLocked: true,
});
await this.setColorObjectInfo(colorRect, fixedLayerObj);
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,
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++){
let print = printTrimsLayers[index];
let id = generateId("layer_image_");
let name = print.name;
let image = await new Promise(resolve => {
fabric.Image.fromURL(print.path, (fabricImage)=>{
const left = flLeft - flWidth * flScaleX / 2 + (print.location?.[0] || 0) * flScaleX
const top = flTop - flHeight * flScaleY / 2 + (print.location?.[1] || 0) * flScaleY
const scaleX = flWidth * (print.scale?.[0] || 1) / fabricImage.width * flScaleX
const scaleY = flHeight * (print.scale?.[1] || 1) / fabricImage.height * flScaleY
const {x, y} = calculateRotatedTopLeftDeg(
fabricImage.width * scaleX,
fabricImage.height * scaleY,
left,
top,
0,
print.angle || 0
)
const angle = print.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,
});
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++){
let print = singleLayers[index];
let id = generateId("layer_image_");
let name = print.name;
let image = await new Promise(resolve => {
fabric.Image.fromURL(print.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" });
})
console.log("==========添加平铺图层", fixedLayerObj.width,image.width)
let scaleX = fixedLayerObj.width / image.width * (print.scale?.[0] || 1) / 5;
let scaleY = fixedLayerObj.height / image.height * (print.scale?.[1] || 1) / 5;
let scale = fixedLayerObj.width > fixedLayerObj.height ? scaleX : scaleY;
let left = (print.location?.[0] || 0) - image.width * scale / 2
let top = (print.location?.[1] || 0) - image.height * scale / 2
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,
fill: new fabric.Pattern({
source: image,
repeat: "repeat",
patternTransform: createPatternTransform(scale, print.angle || 0),
offsetX: left, // 水平偏移
offsetY: top, // 垂直偏移
}),
});
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({});
await this.setColorObjectInfo(groupRect, fixedLayerObj);
// 插入组图层
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);
}
/**
* 缩放红绿图模式内容以适应当前画布大小
* 确保衣服底图和红绿图永远在画布内可见
@@ -1249,6 +1644,7 @@ export class CanvasManager {
return fixedLayer.fabricObject || null;
}
/**
* 获取所有普通图层对象(包括红绿图)
* @returns {Array} 普通图层对象数组
@@ -1315,4 +1711,46 @@ export class CanvasManager {
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);
}
}