523 lines
16 KiB
JavaScript
523 lines
16 KiB
JavaScript
import {
|
||
findObjectById,
|
||
generateId,
|
||
optimizeCanvasRendering,
|
||
} from "../utils/helper.js";
|
||
import { imageModeHandler } from "../utils/imageHelper.js";
|
||
import { LayerType, OperationType } from "../utils/layerHelper.js";
|
||
import { Command, CompositeCommand } from "./Command.js";
|
||
import { fabric } from "fabric-with-all";
|
||
|
||
/**
|
||
* 批量初始化红绿图模式命令
|
||
* 将衣服底图添加到背景层、红绿图添加到固定图层、调整位置和大小,以及设置画布背景为白色等操作合并到一个命令中
|
||
* 减少页面闪烁,一次性渲染完成
|
||
*/
|
||
export class BatchInitializeRedGreenModeCommand extends Command {
|
||
constructor(options = {}) {
|
||
super({
|
||
name: "批量初始化红绿图模式",
|
||
description: "一次性完成红绿图模式的所有初始化操作",
|
||
});
|
||
|
||
this.canvas = options.canvas;
|
||
this.layerManager = options.layerManager;
|
||
this.toolManager = options.toolManager;
|
||
this.clothingImageUrl = options.clothingImageUrl;
|
||
this.redGreenImageUrl = options.redGreenImageUrl;
|
||
this.onImageGenerated = options.onImageGenerated;
|
||
this.normalLayerOpacity = options.normalLayerOpacity || 0.4;
|
||
this.clothingImageOpts = options.clothingImageOpts || null; // 衣服底图选项 - 用于设置图片加载时的选项
|
||
|
||
// 存储原始状态以便撤销
|
||
this.originalCanvasBackground = null;
|
||
this.originalBackgroundObject = null;
|
||
this.originalFixedObjects = null;
|
||
this.originalNormalObjects = null;
|
||
this.originalNormalOpacities = new Map();
|
||
this.originalToolState = null;
|
||
this.originalActiveLayerId = null;
|
||
|
||
// 存储加载的图片对象
|
||
this.clothingImage = null;
|
||
this.redGreenImage = null;
|
||
this.redGreenImageMask = null;
|
||
|
||
// 存储新创建的图层ID
|
||
this.newEmptyLayerId = null;
|
||
}
|
||
|
||
async execute() {
|
||
try {
|
||
await optimizeCanvasRendering(this.canvas, async () => {
|
||
// 1. 设置画布背景为白色
|
||
this.originalCanvasBackground = this.canvas.backgroundColor;
|
||
this.canvas.setBackgroundColor("#ffffff", () => {});
|
||
|
||
// 2. 查找图层结构
|
||
const layers = this.layerManager.layers?.value || [];
|
||
const backgroundLayer = layers.find((layer) => layer.isBackground);
|
||
const fixedLayer = layers.find((layer) => layer.isFixed);
|
||
const normalLayers = layers.filter(
|
||
(layer) => !layer.isBackground && !layer.isFixed
|
||
);
|
||
|
||
if (!backgroundLayer || !fixedLayer || normalLayers.length === 0) {
|
||
throw new Error("缺少必要的图层结构");
|
||
}
|
||
|
||
const normalLayer = normalLayers[0]; // 使用第一个普通图层
|
||
|
||
// 3. 保存原始状态
|
||
this.originalBackgroundObject = backgroundLayer.fabricObject
|
||
? {
|
||
...backgroundLayer.fabricObject,
|
||
ref: backgroundLayer.fabricObject,
|
||
}
|
||
: null;
|
||
|
||
this.originalFixedObjects = fixedLayer.fabricObject
|
||
? [fixedLayer.fabricObject]
|
||
: [];
|
||
|
||
this.originalNormalObjects = normalLayer.fabricObjects
|
||
? [...normalLayer.fabricObjects]
|
||
: [];
|
||
|
||
// 保存当前活动图层ID
|
||
this.originalActiveLayerId = this.layerManager.getActiveLayerId();
|
||
|
||
// 保存普通图层透明度
|
||
normalLayers.forEach((layer) => {
|
||
this.originalNormalOpacities.set(layer.id, layer.opacity || 1);
|
||
if (layer.fabricObjects) {
|
||
layer.fabricObjects.forEach((obj) => {
|
||
this.originalNormalOpacities.set(
|
||
`${layer.id}_${obj.id || "unknown"}`,
|
||
obj.opacity || 1
|
||
);
|
||
});
|
||
}
|
||
});
|
||
|
||
// 保存工具状态
|
||
if (this.toolManager) {
|
||
this.originalToolState = {
|
||
currentTool: this.toolManager.getCurrentTool(),
|
||
isRedGreenMode: this.toolManager.isRedGreenMode,
|
||
};
|
||
}
|
||
|
||
// 5. 并行加载两个图片
|
||
const [clothingImg, redGreenImg] = await Promise.all([
|
||
this._loadImage(this.clothingImageUrl),
|
||
this._loadImage(this.redGreenImageUrl),
|
||
]);
|
||
|
||
// 6. 设置衣服底图到固定图层
|
||
await this._setupClothingImage(clothingImg, fixedLayer);
|
||
|
||
// 7. 设置红绿图到普通图层,位置和大小与衣服底图一致
|
||
await this._setupRedGreenImage(
|
||
redGreenImg,
|
||
normalLayer,
|
||
this.clothingImage
|
||
);
|
||
|
||
// 4. 确保背景图层大小和衣服地图大小一致
|
||
const backgroundObject = await this._setupBackgroundLayer(
|
||
backgroundLayer,
|
||
this.clothingImage
|
||
);
|
||
|
||
// 8. 设置普通图层透明度
|
||
this._setupNormalLayerOpacity(normalLayers); // 这里不需要在这里设置透明度 由图层统一处理
|
||
|
||
// 9. 创建新的空白图层并设置为活动图层
|
||
// this.newEmptyLayerId = await this._createAndActivateEmptyLayer();
|
||
this.newEmptyLayerId = normalLayers[0]?.id || null;
|
||
|
||
// 设置普通图层的裁剪对象为衣服底图
|
||
if (backgroundObject) {
|
||
// const clipPathImg = this.redGreenImage;
|
||
// clipPathImg.set({
|
||
// absolutePositioned: true,
|
||
// });
|
||
|
||
// 克隆衣服底图作为裁剪对象
|
||
this.redGreenImageMask = await new Promise((resolve, reject) => {
|
||
backgroundObject.clone((clonedImg) => {
|
||
if (!clonedImg) {
|
||
reject(new Error("无法克隆红绿图"));
|
||
return;
|
||
}
|
||
resolve(clonedImg);
|
||
});
|
||
});
|
||
|
||
this.redGreenImageMask.set({
|
||
absolutePositioned: true,
|
||
opacity: 0.01, // 设置为几乎透明
|
||
type: "redGreenImageMask",
|
||
id: generateId("redGreenImageMask_"),
|
||
});
|
||
// this.canvas.add(this.redGreenImageMask);
|
||
this.canvas.clipPath = this.redGreenImageMask;
|
||
this.redGreenImageMask.sendToBack();
|
||
this.redGreenImageMask.setCoords();
|
||
|
||
const activeLayer = this.layerManager.getActiveLayer();
|
||
// activeLayer.clippingMask = this.redGreenImageMask.toObject(["id"]);
|
||
activeLayer.opacity = this.normalLayerOpacity;
|
||
// activeLayer?.fabricObjects.forEach((obj) => {
|
||
// obj.set({
|
||
// clipPath: clipPathImg,
|
||
// });
|
||
// });
|
||
}
|
||
|
||
// 10. 配置工具管理器
|
||
this._setupToolManager();
|
||
|
||
console.log("批量红绿图模式初始化完成", {
|
||
衣服底图: this.clothingImageUrl,
|
||
红绿图: this.redGreenImageUrl,
|
||
普通图层透明度: `${Math.round(this.normalLayerOpacity * 100)}%`,
|
||
画布背景: "白色",
|
||
新建空图层ID: this.newEmptyLayerId,
|
||
});
|
||
|
||
await this.layerManager.updateLayersObjectsInteractivity(false);
|
||
});
|
||
|
||
return true;
|
||
} catch (error) {
|
||
// 恢复渲染
|
||
this.canvas.renderOnAddRemove = true;
|
||
console.error("批量红绿图模式初始化失败:", error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建新的空白图层并设置为活动图层
|
||
* @returns {Promise<string>} 新创建的图层ID
|
||
* @private
|
||
*/
|
||
async _createAndActivateEmptyLayer() {
|
||
// 创建新的空白图层
|
||
const newLayerName = "绘制图层";
|
||
const newLayerId = await this.layerManager.createLayer(
|
||
newLayerName,
|
||
LayerType.BITMAP,
|
||
{
|
||
undoable: false,
|
||
}
|
||
);
|
||
|
||
// 设置为活动图层
|
||
if (newLayerId) {
|
||
this.layerManager.setActiveLayer(newLayerId);
|
||
}
|
||
|
||
return newLayerId;
|
||
}
|
||
|
||
async undo() {
|
||
try {
|
||
await optimizeCanvasRendering(this.canvas, async () => {
|
||
// 1. 恢复画布背景
|
||
if (this.originalCanvasBackground !== null) {
|
||
this.canvas.setBackgroundColor(
|
||
this.originalCanvasBackground,
|
||
() => {}
|
||
);
|
||
}
|
||
|
||
// 2. 恢复图层对象
|
||
const layers = this.layerManager.layers?.value || [];
|
||
const backgroundLayer = layers.find((layer) => layer.isBackground);
|
||
const fixedLayer = layers.find((layer) => layer.isFixed);
|
||
const normalLayers = layers.filter(
|
||
(layer) => !layer.isBackground && !layer.isFixed
|
||
);
|
||
|
||
// 移除当前添加的对象
|
||
if (this.clothingImage) {
|
||
this.canvas.remove(this.clothingImage);
|
||
}
|
||
if (this.redGreenImage) {
|
||
this.canvas.remove(this.redGreenImage);
|
||
}
|
||
|
||
// 移除新创建的空白图层
|
||
if (this.newEmptyLayerId) {
|
||
const emptyLayerIndex = layers.findIndex(
|
||
(layer) => layer.id === this.newEmptyLayerId
|
||
);
|
||
if (emptyLayerIndex !== -1) {
|
||
layers.splice(emptyLayerIndex, 1);
|
||
}
|
||
}
|
||
|
||
// 恢复背景图层
|
||
if (backgroundLayer && this.originalBackgroundObject) {
|
||
if (this.originalBackgroundObject.ref) {
|
||
backgroundLayer.fabricObject = this.originalBackgroundObject.ref;
|
||
}
|
||
}
|
||
|
||
// 恢复固定图层
|
||
if (fixedLayer) {
|
||
fixedLayer.fabricObject =
|
||
this.originalFixedObjects.length > 0
|
||
? this.originalFixedObjects[0]
|
||
: null;
|
||
|
||
if (fixedLayer.fabricObject) {
|
||
this.canvas.add(fixedLayer.fabricObject);
|
||
}
|
||
}
|
||
|
||
// 恢复普通图层
|
||
if (normalLayers.length > 0) {
|
||
const normalLayer = normalLayers[0];
|
||
normalLayer.fabricObjects = [...this.originalNormalObjects];
|
||
this.originalNormalObjects.forEach((obj) => {
|
||
this.canvas.add(obj);
|
||
});
|
||
}
|
||
|
||
// 3. 恢复透明度
|
||
normalLayers.forEach((layer) => {
|
||
if (this.originalNormalOpacities.has(layer.id)) {
|
||
layer.opacity = this.originalNormalOpacities.get(layer.id);
|
||
}
|
||
|
||
if (layer.fabricObjects) {
|
||
layer.fabricObjects.forEach((obj) => {
|
||
const key = `${layer.id}_${obj.id || "unknown"}`;
|
||
if (this.originalNormalOpacities.has(key)) {
|
||
obj.opacity = this.originalNormalOpacities.get(key);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// 恢复活动图层
|
||
if (this.originalActiveLayerId) {
|
||
this.layerManager.setActiveLayer(this.originalActiveLayerId);
|
||
}
|
||
|
||
// 4. 恢复工具状态
|
||
if (this.toolManager && this.originalToolState) {
|
||
this.toolManager.isRedGreenMode =
|
||
this.originalToolState.isRedGreenMode;
|
||
if (this.originalToolState.currentTool) {
|
||
this.toolManager.setTool(this.originalToolState.currentTool);
|
||
}
|
||
}
|
||
});
|
||
|
||
return true;
|
||
} catch (error) {
|
||
this.canvas.renderOnAddRemove = true;
|
||
console.error("撤销批量红绿图模式初始化失败:", error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置背景图层
|
||
*/
|
||
async _setupBackgroundLayer(backgroundLayer, clothingImage) {
|
||
let backgroundObject = backgroundLayer.fabricObject;
|
||
let { object } = findObjectById(this.canvas, backgroundObject.id);
|
||
|
||
if (!object) {
|
||
// 创建白色背景矩形
|
||
object = new fabric.Rect({
|
||
left: this.canvas.width / 2,
|
||
top: this.canvas.height / 2,
|
||
width: clothingImage.width,
|
||
height: clothingImage.height,
|
||
scaleX: clothingImage.scaleX,
|
||
scaleY: clothingImage.scaleY,
|
||
fill: "transparent", // 确保背景是透明的
|
||
selectable: false,
|
||
evented: false,
|
||
isBackground: true,
|
||
layerId: backgroundLayer.id,
|
||
layerName: backgroundLayer.name,
|
||
originX: "center",
|
||
originY: "center",
|
||
});
|
||
|
||
this.canvas.add(object);
|
||
this.canvas.sendToBack(object);
|
||
backgroundLayer.fabricObject = object.toObject(["id", "layerId", "type"]);
|
||
} else {
|
||
// 更新现有背景对象大小
|
||
object.set({
|
||
width: clothingImage.width,
|
||
height: clothingImage.height,
|
||
scaleX: clothingImage.scaleX,
|
||
scaleY: clothingImage.scaleY,
|
||
left: this.canvas.width / 2,
|
||
top: this.canvas.height / 2,
|
||
fill: "transparent", // 确保背景是透明的
|
||
});
|
||
}
|
||
|
||
object.setCoords();
|
||
|
||
return object;
|
||
}
|
||
|
||
/**
|
||
* 加载图片
|
||
*/
|
||
async _loadImage(imageUrl) {
|
||
return new Promise((resolve, reject) => {
|
||
fabric.Image.fromURL(
|
||
imageUrl,
|
||
(img) => {
|
||
if (!img) {
|
||
reject(new Error(`无法加载图片: ${imageUrl}`));
|
||
return;
|
||
}
|
||
resolve(img);
|
||
},
|
||
{ crossOrigin: "anonymous" }
|
||
);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 设置衣服底图
|
||
*/
|
||
async _setupClothingImage(img, fixedLayer) {
|
||
if (this.clothingImageOpts?.imageMode) {
|
||
// 如果有衣服底图选项,应用这些选项
|
||
// 底图加载方式 1.平铺 2.拉伸 3.拉伸平铺 4.拉伸平铺并裁剪 5.包含
|
||
// this.clothingImageOpts?.imageMode // 默认不处理 可选 contains, stretch,tile, stretchTile, stretchTileCrop
|
||
// 通用处理图片模式
|
||
imageModeHandler({
|
||
imageMode: this.clothingImageOpts?.imageMode,
|
||
newImage: img,
|
||
canvasWidth: this.canvas.width,
|
||
canvasHeight: this.canvas.height,
|
||
});
|
||
} else {
|
||
// 计算图片缩放,保持上下留边距
|
||
const margin = 50;
|
||
const maxWidth = this.canvas.width - margin * 2;
|
||
const maxHeight = this.canvas.height - margin * 2;
|
||
const scale = Math.min(maxWidth / img.width, maxHeight / img.height);
|
||
img.set({
|
||
scaleX: scale,
|
||
scaleY: scale,
|
||
});
|
||
}
|
||
|
||
img.set({
|
||
left: this.canvas.width / 2,
|
||
top: this.canvas.height / 2,
|
||
originX: "center",
|
||
originY: "center",
|
||
selectable: false,
|
||
evented: false,
|
||
layerId: fixedLayer.id,
|
||
layerName: fixedLayer.name,
|
||
id: generateId("clothingImage"),
|
||
});
|
||
|
||
// 清除固定图层原有内容
|
||
if (fixedLayer.fabricObject) {
|
||
const { object } = findObjectById(
|
||
this.canvas,
|
||
fixedLayer.fabricObject.id
|
||
);
|
||
if (object) this.canvas.remove(object);
|
||
}
|
||
|
||
// 添加到画布和固定图层
|
||
this.canvas.add(img);
|
||
fixedLayer.fabricObject = img.toObject(["id", "type", "layerId"]);
|
||
this.clothingImage = img;
|
||
}
|
||
|
||
/**
|
||
* 设置红绿图
|
||
*/
|
||
async _setupRedGreenImage(img, normalLayer, clothingImage) {
|
||
if (!clothingImage) {
|
||
throw new Error("衣服底图未加载,无法设置红绿图位置");
|
||
}
|
||
|
||
if (this.clothingImageOpts?.imageMode) {
|
||
imageModeHandler({
|
||
imageMode: this.clothingImageOpts?.imageMode,
|
||
newImage: img,
|
||
canvasWidth: this.canvas.width,
|
||
canvasHeight: this.canvas.height,
|
||
});
|
||
} else {
|
||
// 使用与衣服底图完全相同的属性
|
||
img.set({
|
||
scaleX: clothingImage.scaleX,
|
||
scaleY: clothingImage.scaleY,
|
||
});
|
||
}
|
||
|
||
img.set({
|
||
left: clothingImage.left,
|
||
top: clothingImage.top,
|
||
originX: clothingImage.originX,
|
||
originY: clothingImage.originY,
|
||
selectable: false,
|
||
evented: false,
|
||
layerId: normalLayer.id,
|
||
layerName: normalLayer.name,
|
||
id: generateId("redGreenImage"),
|
||
});
|
||
|
||
// 清除普通图层原有内容
|
||
if (normalLayer.fabricObjects) {
|
||
normalLayer.fabricObjects.forEach((obj) => {
|
||
const { object } = findObjectById(this.canvas, obj.id);
|
||
if (object) {
|
||
this.canvas.remove(object);
|
||
}
|
||
});
|
||
}
|
||
// 添加到画布和普通图层
|
||
this.canvas.add(img);
|
||
normalLayer.fabricObjects = [img.toObject(["id", "type", "layerId"])];
|
||
this.redGreenImage = img;
|
||
}
|
||
|
||
/**
|
||
* 设置普通图层透明度
|
||
*/
|
||
_setupNormalLayerOpacity(normalLayers) {
|
||
normalLayers.forEach((layer) => {
|
||
// 设置图层透明度
|
||
layer.opacity = this.normalLayerOpacity;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 设置工具管理器
|
||
*/
|
||
_setupToolManager() {
|
||
if (this.toolManager) {
|
||
// 设置红绿图模式
|
||
this.toolManager.isRedGreenMode = true;
|
||
|
||
// 切换到红色笔刷工具
|
||
this.toolManager.setTool(OperationType.RED_BRUSH);
|
||
}
|
||
}
|
||
}
|