Files
aida_front/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js
2025-07-10 10:13:24 +08:00

523 lines
16 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 {
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);
}
}
}