合并画布代码

This commit is contained in:
X1627315083
2025-06-18 11:05:23 +08:00
parent 903c0ebdf5
commit 9c7fae36eb
118 changed files with 23633 additions and 8201 deletions

View File

@@ -1,4 +1,4 @@
//import { fabric } from "fabric-with-all";
import { fabric } from "fabric-with-all";
import initAligningGuidelines, {
initCenteringGuidelines,
} from "../utils/helperLine";
@@ -14,10 +14,15 @@ 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, optimizeCanvasRendering } from "../utils/helper";
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
import { isFunction } from "lodash-es";
import {
ChangeFixedImageCommand,
AddImageToLayerCommand,
} from "../commands/ObjectLayerCommands";
restoreObjectLayerAssociations,
simplifyLayers,
validateLayerAssociations,
} from "../utils/layerUtils";
export class CanvasManager {
constructor(canvasElement, options) {
@@ -34,6 +39,7 @@ export class CanvasManager {
this.canvasHeight = options.canvasHeight || this.height; // 画布高度
this.canvasColor = options.canvasColor || "#ffffff"; // 画布背景颜色
this.enabledRedGreenMode = options.enabledRedGreenMode || false; // 是否启用红绿图模式
this.eraserStateManager = null; // 橡皮擦状态管理器引用
// 初始化画布
this.initializeCanvas();
}
@@ -65,14 +71,6 @@ export class CanvasManager {
layers: this.layers,
});
// 初始化红绿图模式管理器
this.redGreenModeManager = new RedGreenModeManager({
canvas: this.canvas,
layerManager: null, // 稍后设置
toolManager: null, // 稍后设置
commandManager: null, // 稍后设置
});
// 设置画布辅助线
initAligningGuidelines(this.canvas);
@@ -90,7 +88,7 @@ export class CanvasManager {
*/
_initCanvasEvents() {
// 添加笔刷图像转换处理回调
this.canvas.onBrushImageConverted = (fabricImage) => {
this.canvas.onBrushImageConverted = async (fabricImage) => {
// 如果图层管理器存在,将图像合并到当前活动图层
if (this.layerManager) {
// 获取当前活动图层
@@ -107,21 +105,49 @@ export class CanvasManager {
});
// 执行高保真合并操作
this.eventManager?.mergeLayerObjectsForPerformance?.({
await this.eventManager?.mergeLayerObjectsForPerformance?.({
fabricImage,
activeLayer,
});
// 返回false表示不要自动添加到画布因为我们已经通过图层管理器处理了
return false;
// 返回true表示不要自动添加到画布因为我们已经通过图层管理器处理了
return true;
} else {
console.warn("没有活动图层,使用默认行为添加图像");
}
}
// 返回true表示使用默认行为直接添加到画布
return true;
// 返回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?.());
});
}
/**
@@ -168,6 +194,10 @@ export class CanvasManager {
if (this.redGreenModeManager) {
this.redGreenModeManager.layerManager = this.layerManager;
}
if (this.eraserStateManager) {
this.eraserStateManager.setLayerManager(this.layerManager);
}
}
/**
@@ -320,7 +350,8 @@ export class CanvasManager {
/**
* 居中所有画布元素
* 计算所有对象的边界框,然后将它们整体居中显示
* 以背景层为参照,计算背景层的偏移量并应用到所有对象上
* 这样可以保持对象间的相对位置关系不变
*/
centerAllObjects() {
if (!this.canvas) return;
@@ -333,45 +364,56 @@ export class CanvasManager {
(obj) => obj.visible !== false && !obj.excludeFromExport
);
// 如果只有背景层或没有可见对象,只居中背景层
if (
visibleObjects.length === 0 ||
(visibleObjects.length === 1 && visibleObjects[0].isBackground)
) {
// 尝试居中背景层
this.centerBackgroundLayer(this.width, this.height);
return;
}
// 如果没有可见对象,直接返回
if (visibleObjects.length === 0) return;
// 单独处理背景层
// 获取背景对象
const backgroundObject = visibleObjects.find((obj) => obj.isBackground);
const contentObjects = backgroundObject
? visibleObjects.filter((obj) => obj !== backgroundObject)
: visibleObjects;
// 如果只有背景层,居中背景层
if (contentObjects.length === 0 && backgroundObject) {
this.centerBackgroundLayer(this.width, this.height);
// 如果只有背景层或没有背景层,使用原有逻辑
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 bounds = this._calculateObjectsBounds(contentObjects);
// 计算所有对象的中心点
const objectsCenterX = bounds.left + bounds.width / 2;
const objectsCenterY = bounds.top + bounds.height / 2;
// 记录背景层居中前的位置
const backgroundOldLeft = backgroundObject.left;
const backgroundOldTop = backgroundObject.top;
// 计算画布中心点
const canvasCenterX = this.width / 2;
const canvasCenterY = this.height / 2;
// 计算需要移动的距离
const deltaX = canvasCenterX - objectsCenterX;
const deltaY = canvasCenterY - objectsCenterY;
// 设置背景层居中
backgroundObject.set({
left: canvasCenterX,
top: canvasCenterY,
originX: "center",
originY: "center",
});
// 移动所有对象,包括背景层
visibleObjects.forEach((obj) => {
// 计算背景层的偏移量
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,
@@ -456,6 +498,7 @@ export class CanvasManager {
// 如果需要裁剪背景层以外的内容,则更新蒙层位置
// 创建或更新蒙层
CanvasConfig.isCropBackground &&
!this.enabledRedGreenMode &&
this.createOrUpdateMask(backgroundLayerObject);
return true;
}
@@ -480,12 +523,13 @@ export class CanvasManager {
// 创建蒙层 - 使用透明矩形作为裁剪区域
this.maskLayer = new fabric.Rect({
id: "canvasMaskLayer",
width: bgWidth,
height: bgHeight,
left: left,
top: top,
fill: "transparent",
stroke: "#cccccc",
stroke: "transparent",
strokeWidth: 1,
strokeDashArray: [5, 5],
selectable: false,
@@ -504,20 +548,12 @@ export class CanvasManager {
this.canvas.clipPath = new fabric.Rect({
width: bgWidth,
height: bgHeight,
left: 0,
top: 0,
left: left,
top: top,
originX: backgroundLayerObject.originX || "left",
originY: backgroundLayerObject.originY || "top",
absolutePositioned: true,
});
// 设置蒙层位置
this.canvas.clipPath.set({
left: left,
top: top,
});
this.canvas.renderAll();
}
getBackgroundLayer() {
if (!this.canvas) return null;
@@ -655,6 +691,39 @@ export class CanvasManager {
});
}
/**
*
* 更改固定图层的图片
* @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
});
command.undoable =
options.undoable !== undefined ? options.undoable : false; // 默认不可撤销 undoable = true 为可撤销
return (
(await command?.execute?.()) || {
success: false,
layerId: null,
imageUrl: null,
}
);
}
/**
* 导出图片
* @param {Object} options 导出选项
@@ -663,6 +732,7 @@ export class CanvasManager {
* @param {String} options.layerId 导出具体图层ID
* @param {Array} options.layerIdArray 导出多个图层ID数组
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
* @param {Boolean} options.restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
* @returns {String} 导出的图片数据URL
*/
exportImage(options = {}) {
@@ -672,7 +742,39 @@ export class CanvasManager {
}
try {
return this.exportManager.exportImage(options);
// 自动设置红绿图模式相关参数
const enhancedOptions = {
...options,
// 如果没有明确指定,则根据当前模式自动设置
restoreOpacityInRedGreen:
options.restoreOpacityInRedGreen !== undefined
? options.restoreOpacityInRedGreen
: true, // 默认在红绿图模式下恢复透明度
};
// 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层
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.visible
)
?.map((layer) => layer.id) || [];
if (normalLayerIds.length > 0) {
enhancedOptions.layerIdArray = normalLayerIds;
console.log("红绿图模式导出图层:", normalLayerIds);
}
}
return this.exportManager.exportImage(enhancedOptions);
} catch (error) {
console.error("CanvasManager导出图片失败:", error);
throw error;
@@ -709,129 +811,129 @@ export class CanvasManager {
}
getJSON() {
// 简化图层数据在loadJSON时要根据id恢复引用
let tempLayers = this.layers ? this.layers.value : [];
// // 简化图层数据在loadJSON时要根据id恢复引用
// let tempLayers = this.layers ? this.layers.value : [];
// // 创建对象ID映射表用于快速查找
// tempLayers = tempLayers.map((layer) => {
// const newLayer = { ...layer };
// 为所有fabric对象生成ID如果没有的话
const canvasObjects = this.canvas.getObjects();
canvasObjects.forEach((obj) => {
if (!obj.id) {
obj.id = `obj_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
}
});
// // 处理fabricObjects数组
// if (Array.isArray(layer.fabricObjects)) {
// newLayer.fabricObjects = layer.fabricObjects
// .map((item) => {
// if (!item) return null;
// 创建对象ID映射表,用于快速查找
const objectIdMap = new Map();
canvasObjects.forEach((obj) => {
if (obj.id) {
objectIdMap.set(obj, obj.id);
}
});
// // 确保对象ID
// if (!item.id) {
// item.id = `obj_${Date.now()}_${Math.floor(
// Math.random() * 10000
// )}`;
// }
tempLayers = tempLayers.map((layer) => {
const newLayer = { ...layer };
// return {
// id: item.id,
// type: item.type || "object", // 保存类型信息用于调试
// };
// })
// .filter((item) => item !== null);
// } else {
// newLayer.fabricObjects = [];
// }
// 处理fabricObjects数组
if (Array.isArray(layer.fabricObjects)) {
newLayer.fabricObjects = layer.fabricObjects
.map((item) => {
if (!item) return null;
// if (layer.clippingMask) {
// layer.clippingMask = {
// id: layer.clippingMask.id,
// };
// }
// 确保对象有ID
if (!item.id) {
item.id = `obj_${Date.now()}_${Math.floor(
Math.random() * 10000
)}`;
}
// // 处理单个fabricObject
// if (layer.fabricObject) {
// if (!layer.fabricObject.id) {
// layer.fabricObject.id = `obj_${Date.now()}_${Math.floor(
// Math.random() * 10000
// )}`;
// }
// newLayer.fabricObject = {
// id: layer.fabricObject.id,
// type: layer.fabricObject.type || "object",
// };
// } else {
// newLayer.fabricObject = null;
// }
return {
id: item.id,
type: item.type || "object", // 保存类型信息用于调试
};
})
.filter((item) => item !== null);
} else {
newLayer.fabricObjects = [];
}
// // 处理子图层
// if (Array.isArray(layer.children)) {
// newLayer.children = layer.children.map((cItem) => {
// const newChild = { ...cItem };
// 处理单个fabricObject
if (layer.fabricObject) {
if (!layer.fabricObject.id) {
layer.fabricObject.id = `obj_${Date.now()}_${Math.floor(
Math.random() * 10000
)}`;
}
newLayer.fabricObject = {
id: layer.fabricObject.id,
type: layer.fabricObject.type || "object",
};
} else {
newLayer.fabricObject = null;
}
// // 处理子图层的fabricObjects
// if (Array.isArray(cItem.fabricObjects)) {
// newChild.fabricObjects = cItem.fabricObjects
// .map((item) => {
// if (!item) return null;
// 处理子图层
if (Array.isArray(layer.children)) {
newLayer.children = layer.children.map((cItem) => {
const newChild = { ...cItem };
// if (!item.id) {
// item.id = `obj_${Date.now()}_${Math.floor(
// Math.random() * 10000
// )}`;
// }
// 处理子图层的fabricObjects
if (Array.isArray(cItem.fabricObjects)) {
newChild.fabricObjects = cItem.fabricObjects
.map((item) => {
if (!item) return null;
// return {
// id: item.id,
// type: item.type || "object",
// };
// })
// .filter((item) => item !== null);
// } else {
// newChild.fabricObjects = [];
// }
if (!item.id) {
item.id = `obj_${Date.now()}_${Math.floor(
Math.random() * 10000
)}`;
}
// // 处理子图层的fabricObject
// if (cItem.fabricObject) {
// if (!cItem.fabricObject.id) {
// cItem.fabricObject.id = `obj_${Date.now()}_${Math.floor(
// Math.random() * 10000
// )}`;
// }
// newChild.fabricObject = {
// id: cItem.fabricObject.id,
// type: cItem.fabricObject.type || "object",
// };
// } else {
// newChild.fabricObject = null;
// }
return {
id: item.id,
type: item.type || "object",
};
})
.filter((item) => item !== null);
} else {
newChild.fabricObjects = [];
}
// 处理子图层的fabricObject
if (cItem.fabricObject) {
if (!cItem.fabricObject.id) {
cItem.fabricObject.id = `obj_${Date.now()}_${Math.floor(
Math.random() * 10000
)}`;
}
newChild.fabricObject = {
id: cItem.fabricObject.id,
type: cItem.fabricObject.type || "object",
};
} else {
newChild.fabricObject = null;
}
return newChild;
});
} else {
newLayer.children = [];
}
return newLayer;
});
// return newChild;
// });
// } else {
// newLayer.children = [];
// }
// return newLayer;
// });
try {
console.log(
"获取画布JSON数据...",
simplifyLayers(JSON.parse(JSON.stringify(this.layers.value)))
);
return JSON.stringify({
canvas: this.canvas.toJSON([
"id",
"type",
"layerId",
"layerName",
"isBackground",
"isLocked",
"isVisible",
"isFixed",
"parentId",
"excludeFromExport",
"eraser",
"eraserable",
"erasable",
]),
layers: tempLayers,
layers: JSON.stringify(
simplifyLayers(JSON.parse(JSON.stringify(this.layers.value)))
), // 简化图层数据
version: "1.0", // 添加版本信息
timestamp: new Date().toISOString(), // 添加时间戳
canvasWidth: this.canvasWidth.value,
@@ -845,7 +947,7 @@ export class CanvasManager {
}
}
loadJSON(json) {
loadJSON(json, calllBack) {
console.log("加载画布JSON数据:", json);
// 确保传入的json是字符串格式
@@ -859,7 +961,7 @@ export class CanvasManager {
const parsedJson = JSON.parse(json);
return new Promise((resolve, reject) => {
const tempLayers = parsedJson?.layers || [];
const tempLayers = JSON.parse(parsedJson?.layers) || [];
const canvasData = parsedJson?.canvas;
if (!tempLayers) {
@@ -872,9 +974,11 @@ export class CanvasManager {
return;
}
this.canvasWidth.value = parsedJson.canvasWidth || this.width;
this.canvasHeight.value = parsedJson.canvasHeight || this.height;
this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor;
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);
@@ -885,107 +989,57 @@ export class CanvasManager {
this.canvas.clear();
// 加载画布数据
this.canvas.loadFromJSON(canvasData, () => {
this.backgroundColor = parsedJson.backgroundColor || "#ffffff";
try {
// 重置画布数据
this.setCanvasSize(this.canvas.width, this.canvas.height);
this.canvas.loadFromJSON(canvasData, async () => {
await optimizeCanvasRendering(this.canvas, async () => {
this.backgroundColor = parsedJson.backgroundColor || "#ffffff";
try {
// 重置画布数据
this.setCanvasSize(this.canvas.width, this.canvas.height);
// 创建对象ID映射表用于快速查找
const objectIdMap = new Map();
const canvasObjects = this.canvas.getObjects();
// 重新构建对象关系
restoreObjectLayerAssociations(
this.layers.value,
this.canvas.getObjects()
);
canvasObjects.forEach((obj) => {
if (obj.id) {
objectIdMap.set(obj.id, obj);
}
});
// 验证图层关联关系 - 稳定后可以注释
const isValidate = validateLayerAssociations(
this.layers.value,
this.canvas.getObjects()
);
// 辅助函数根据ID查找对象
const findObjectById = (id) => {
if (!id) return null;
return objectIdMap.get(id) || null;
};
console.log("图层关联验证结果:", isValidate);
// 恢复图层数据
this.layers.value = tempLayers.map((layer) => {
const restoredLayer = { ...layer };
this.canvas.activeLayerId.value =
parsedJson?.activeLayerId || this.layers.value[0]?.id || null;
// 恢复fabricObjects数组
if (Array.isArray(layer.fabricObjects)) {
restoredLayer.fabricObjects = layer.fabricObjects
.map((item) => {
if (!item || !item.id) return null;
return findObjectById(item.id);
})
.filter((obj) => obj !== null);
} else {
restoredLayer.fabricObjects = [];
}
// // 如果检测到红绿图模式内容,进行缩放调整
// if (this.enabledRedGreenMode) {
// this._rescaleRedGreenModeContent();
// }
// 恢复单个fabricObject
if (layer.fabricObject && layer.fabricObject.id) {
restoredLayer.fabricObject = findObjectById(
layer.fabricObject.id
);
} else {
restoredLayer.fabricObject = null;
}
// 重载代码后支持回调中操作一些内容
await calllBack?.();
// 恢复子图层
if (Array.isArray(layer.children)) {
restoredLayer.children = layer.children.map((cItem) => {
const restoredChild = { ...cItem };
// 确保所有对象的交互性正确设置
await this.layerManager?.updateLayersObjectsInteractivity?.(
false
);
console.log(this.layerManager.layers.value);
debugger;
// 恢复子图层的fabricObjects
if (Array.isArray(cItem.fabricObjects)) {
restoredChild.fabricObjects = cItem.fabricObjects
.map((item) => {
if (!item || !item.id) return null;
return findObjectById(item.id);
})
.filter((obj) => obj !== null);
} else {
restoredChild.fabricObjects = [];
}
// 更新所有缩略图
setTimeout(() => {
this.updateAllThumbnails();
}, 100);
// 恢复子图层的fabricObject
if (cItem.fabricObject && cItem.fabricObject.id) {
restoredChild.fabricObject = findObjectById(
cItem.fabricObject.id
);
} else {
restoredChild.fabricObject = null;
}
return restoredChild;
});
} else {
restoredLayer.children = [];
}
return restoredLayer;
});
this.canvas.activeLayerId.value =
parsedJson?.activeLayerId || this.layers.value[0]?.id || null;
// 如果检测到红绿图模式内容,进行缩放调整
if (this.enabledRedGreenMode) {
this._rescaleRedGreenModeContent();
console.log("画布JSON数据加载完成");
resolve();
} catch (error) {
console.error("恢复图层数据失败:", error);
reject(new Error("恢复图层数据失败: " + error.message));
}
// 更新所有缩略图
setTimeout(() => {
this.updateAllThumbnails();
}, 100);
console.log("画布JSON数据加载完成");
resolve();
} catch (error) {
console.error("恢复图层数据失败:", error);
reject(new Error("恢复图层数据失败: " + error.message));
}
});
});
});
} catch (error) {

View File

@@ -0,0 +1,98 @@
import { EraserCommand } from "../commands/EraserCommand";
/**
* 橡皮擦状态管理器
* 用于管理橡皮擦操作的状态快照
*/
export class EraserStateManager {
constructor(canvas, layerManager) {
this.canvas = canvas;
this.layerManager = layerManager;
this.currentSnapshot = null;
this.pendingCommand = null;
}
setLayerManager(layerManager) {
this.layerManager = layerManager;
}
/**
* 开始橡皮擦操作 - 捕获初始状态
*/
startErasing() {
console.log("橡皮擦操作开始 - 捕获状态快照");
this.currentSnapshot = this._captureCanvasSnapshot();
}
/**
* 结束橡皮擦操作 - 创建命令
* @param {Array} affectedObjects 受影响的对象
* @returns {EraserCommand|null} 创建的橡皮擦命令
*/
endErasing(affectedObjects = []) {
if (!this.currentSnapshot) {
console.warn("没有初始状态快照,无法创建橡皮擦命令");
return null;
}
if (!affectedObjects || affectedObjects.length === 0) {
console.log("没有对象被擦除,不创建命令");
this.currentSnapshot = null;
return null;
}
console.log(`橡皮擦操作结束 - 影响了 ${affectedObjects.length} 个对象`);
// 捕获擦除后的状态
const afterSnapshot = this._captureCanvasSnapshot();
// 创建橡皮擦命令
const command = new EraserCommand({
canvas: this.canvas,
layerManager: this.layerManager,
affectedObjects: affectedObjects,
beforeSnapshot: this.currentSnapshot,
afterSnapshot: afterSnapshot,
});
// 重置状态
this.currentSnapshot = null;
return command;
}
/**
* 捕获画布状态快照
* @returns {Object} 画布状态快照
* @private
*/
_captureCanvasSnapshot() {
try {
return this.canvas.toJSON([
"id",
"type",
"layerId",
"layerName",
"isBackground",
"isLocked",
"isVisible",
"isFixed",
"parentId",
"eraser",
"eraserable",
"erasable",
]);
} catch (error) {
console.error("捕获画布状态快照失败:", error);
return null;
}
}
/**
* 取消当前擦除操作
*/
cancelErasing() {
this.currentSnapshot = null;
this.pendingCommand = null;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,441 @@
/**
* 液化对象引用管理器
* 专门处理液化操作中的对象引用管理,避免引用丢失问题
*/
export class LiquifyReferenceManager {
constructor() {
// 对象引用池
this.objectRefs = new Map();
// 状态快照池
this.stateSnapshots = new Map();
// ImageData缓存池
this.imageDataCache = new Map();
// 事件监听器备份
this.eventListeners = new Map();
}
/**
* 注册对象到引用管理器
* @param {Object} fabricObject Fabric对象
* @param {String} objectId 对象唯一ID
* @returns {String} 引用ID
*/
registerObject(fabricObject, objectId) {
const refId = objectId || this._generateRefId();
// 保存对象引用
this.objectRefs.set(refId, fabricObject);
// 备份事件监听器
this._backupEventListeners(refId, fabricObject);
// 创建初始状态快照
this._createStateSnapshot(refId, fabricObject);
console.log(`📝 对象已注册到引用管理器: ${refId}`);
return refId;
}
/**
* 获取对象引用
* @param {String} refId 引用ID
* @returns {Object|null} Fabric对象
*/
getObjectRef(refId) {
return this.objectRefs.get(refId) || null;
}
/**
* 更新对象的图像数据,保持引用不变
* @param {String} refId 引用ID
* @param {ImageData} newImageData 新的图像数据
* @returns {Promise<Boolean>} 更新结果
*/
async updateObjectImageData(refId, newImageData) {
const fabricObject = this.objectRefs.get(refId);
if (!fabricObject || !newImageData) {
throw new Error(`无法更新对象图像数据: 对象不存在或数据无效 (${refId})`);
}
return new Promise((resolve, reject) => {
try {
// 创建临时canvas
const tempCanvas = document.createElement("canvas");
tempCanvas.width = newImageData.width;
tempCanvas.height = newImageData.height;
const tempCtx = tempCanvas.getContext("2d");
tempCtx.putImageData(newImageData, 0, 0);
// 创建新的图像元素
const img = new Image();
img.onload = () => {
try {
// 保存当前状态
const currentState = this._captureObjectState(fabricObject);
// 更新图像源
if (fabricObject.setElement) {
fabricObject.setElement(img);
} else if (fabricObject._element) {
fabricObject._element = img;
fabricObject._originalElement = img;
}
// 恢复非图像属性
this._restoreObjectState(fabricObject, currentState);
// 标记需要重新渲染
fabricObject.dirty = true;
fabricObject.setCoords();
// 更新缓存
this._updateImageDataCache(refId, newImageData);
console.log(`✅ 对象图像数据更新成功: ${refId}`);
resolve(true);
} catch (error) {
console.error("更新对象状态失败:", error);
reject(error);
}
};
img.onerror = () => {
reject(new Error("加载图像数据失败"));
};
img.src = tempCanvas.toDataURL();
} catch (error) {
console.error("创建临时canvas失败:", error);
reject(error);
}
});
}
/**
* 创建对象状态快照
* @param {String} refId 引用ID
* @param {String} snapshotId 快照ID
* @returns {String} 快照ID
*/
createSnapshot(refId, snapshotId = null) {
const fabricObject = this.objectRefs.get(refId);
if (!fabricObject) {
throw new Error(`无法创建快照: 对象不存在 (${refId})`);
}
const snapId = snapshotId || `${refId}_${Date.now()}`;
const snapshot = this._createStateSnapshot(snapId, fabricObject);
console.log(`📸 已创建对象快照: ${snapId}`);
return snapId;
}
/**
* 恢复对象到指定快照状态
* @param {String} refId 引用ID
* @param {String} snapshotId 快照ID
* @returns {Promise<Boolean>} 恢复结果
*/
async restoreFromSnapshot(refId, snapshotId) {
const fabricObject = this.objectRefs.get(refId);
const snapshot = this.stateSnapshots.get(snapshotId);
if (!fabricObject || !snapshot) {
throw new Error(
`无法恢复快照: 对象或快照不存在 (${refId}, ${snapshotId})`
);
}
// 恢复图像数据
if (snapshot.imageData) {
await this.updateObjectImageData(refId, snapshot.imageData);
}
// 恢复对象属性
if (snapshot.properties) {
this._restoreObjectState(fabricObject, snapshot.properties);
}
// 恢复事件监听器
if (snapshot.eventListeners) {
this._restoreEventListeners(refId, fabricObject, snapshot.eventListeners);
}
console.log(`🔄 对象快照恢复成功: ${refId} -> ${snapshotId}`);
return true;
}
/**
* 批量更新多个对象
* @param {Array} updates 更新列表 [{refId, imageData}, ...]
* @returns {Promise<Array>} 更新结果
*/
async batchUpdate(updates) {
const results = [];
for (const update of updates) {
try {
const result = await this.updateObjectImageData(
update.refId,
update.imageData
);
results.push({ refId: update.refId, success: true, result });
} catch (error) {
console.error(`批量更新失败 ${update.refId}:`, error);
results.push({
refId: update.refId,
success: false,
error: error.message,
});
}
}
return results;
}
/**
* 清理不再使用的引用
* @param {String} refId 引用ID
*/
cleanup(refId) {
this.objectRefs.delete(refId);
this.stateSnapshots.delete(refId);
this.imageDataCache.delete(refId);
this.eventListeners.delete(refId);
console.log(`🗑️ 已清理对象引用: ${refId}`);
}
/**
* 清理所有引用
*/
cleanupAll() {
this.objectRefs.clear();
this.stateSnapshots.clear();
this.imageDataCache.clear();
this.eventListeners.clear();
console.log("🗑️ 已清理所有对象引用");
}
/**
* 获取内存使用统计
* @returns {Object} 统计信息
*/
getMemoryStats() {
return {
objectRefs: this.objectRefs.size,
stateSnapshots: this.stateSnapshots.size,
imageDataCache: this.imageDataCache.size,
eventListeners: this.eventListeners.size,
totalMemoryUsage: this._calculateMemoryUsage(),
};
}
// 私有方法
/**
* 生成引用ID
* @returns {String} 引用ID
* @private
*/
_generateRefId() {
return `ref_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 备份事件监听器
* @param {String} refId 引用ID
* @param {Object} fabricObject Fabric对象
* @private
*/
_backupEventListeners(refId, fabricObject) {
const listeners = {};
// 备份常见的事件监听器
const eventTypes = [
"mousedown",
"mouseup",
"mousemove",
"mouseout",
"mouseover",
];
eventTypes.forEach((eventType) => {
if (
fabricObject.__eventListeners &&
fabricObject.__eventListeners[eventType]
) {
listeners[eventType] = [...fabricObject.__eventListeners[eventType]];
}
});
this.eventListeners.set(refId, listeners);
}
/**
* 恢复事件监听器
* @param {String} refId 引用ID
* @param {Object} fabricObject Fabric对象
* @param {Object} listeners 监听器备份
* @private
*/
_restoreEventListeners(refId, fabricObject, listeners) {
Object.keys(listeners).forEach((eventType) => {
if (listeners[eventType] && listeners[eventType].length > 0) {
listeners[eventType].forEach((listener) => {
fabricObject.on(eventType, listener);
});
}
});
}
/**
* 创建状态快照
* @param {String} snapId 快照ID
* @param {Object} fabricObject Fabric对象
* @returns {Object} 快照数据
* @private
*/
_createStateSnapshot(snapId, fabricObject) {
const snapshot = {
timestamp: Date.now(),
properties: this._captureObjectState(fabricObject),
imageData: this._captureImageData(fabricObject),
eventListeners: this.eventListeners.get(snapId.split("_")[0]) || {},
};
this.stateSnapshots.set(snapId, snapshot);
return snapshot;
}
/**
* 捕获对象状态
* @param {Object} fabricObject Fabric对象
* @returns {Object} 对象状态
* @private
*/
_captureObjectState(fabricObject) {
return {
left: fabricObject.left,
top: fabricObject.top,
scaleX: fabricObject.scaleX,
scaleY: fabricObject.scaleY,
angle: fabricObject.angle,
flipX: fabricObject.flipX,
flipY: fabricObject.flipY,
opacity: fabricObject.opacity,
visible: fabricObject.visible,
selectable: fabricObject.selectable,
evented: fabricObject.evented,
id: fabricObject.id,
layerId: fabricObject.layerId,
customData: fabricObject.customData ? { ...fabricObject.customData } : {},
filters: fabricObject.filters ? [...fabricObject.filters] : [],
};
}
/**
* 恢复对象状态
* @param {Object} fabricObject Fabric对象
* @param {Object} state 状态数据
* @private
*/
_restoreObjectState(fabricObject, state) {
if (!state) return;
fabricObject.set({
left: state.left,
top: state.top,
scaleX: state.scaleX,
scaleY: state.scaleY,
angle: state.angle,
flipX: state.flipX,
flipY: state.flipY,
opacity: state.opacity,
visible: state.visible,
selectable: state.selectable,
evented: state.evented,
});
// 恢复自定义属性
if (state.customData) {
fabricObject.customData = { ...state.customData };
}
// 恢复滤镜
if (state.filters && state.filters.length > 0) {
fabricObject.filters = [...state.filters];
fabricObject.applyFilters();
}
}
/**
* 捕获图像数据
* @param {Object} fabricObject Fabric对象
* @returns {ImageData|null} 图像数据
* @private
*/
_captureImageData(fabricObject) {
try {
if (
fabricObject._element &&
fabricObject._element.width &&
fabricObject._element.height
) {
const canvas = document.createElement("canvas");
canvas.width = fabricObject._element.width;
canvas.height = fabricObject._element.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(fabricObject._element, 0, 0);
return ctx.getImageData(0, 0, canvas.width, canvas.height);
}
} catch (error) {
console.warn("无法捕获图像数据:", error);
}
return null;
}
/**
* 更新图像数据缓存
* @param {String} refId 引用ID
* @param {ImageData} imageData 图像数据
* @private
*/
_updateImageDataCache(refId, imageData) {
this.imageDataCache.set(refId, {
data: imageData,
timestamp: Date.now(),
width: imageData.width,
height: imageData.height,
});
}
/**
* 计算内存使用量(近似值)
* @returns {Number} 内存使用量(字节)
* @private
*/
_calculateMemoryUsage() {
let totalBytes = 0;
// 计算ImageData缓存大小
this.imageDataCache.forEach((cache) => {
totalBytes += cache.width * cache.height * 4; // RGBA = 4 bytes per pixel
});
return totalBytes;
}
}
/**
* 创建单例引用管理器
*/
let liquifyReferenceManagerInstance = null;
export function getLiquifyReferenceManager() {
if (!liquifyReferenceManagerInstance) {
liquifyReferenceManagerInstance = new LiquifyReferenceManager();
}
return liquifyReferenceManagerInstance;
}

View File

@@ -1,4 +1,4 @@
//import { fabric } from "fabric-with-all";
import { fabric } from "fabric-with-all";
import { createLayer, LayerType, OperationType } from "../utils/layerHelper.js";
import { BatchInitializeRedGreenModeCommand } from "../commands/RedGreenCommands.js";
@@ -35,7 +35,6 @@ export class RedGreenModeManager {
* @param {String} options.redGreenImageUrl 红绿图URL
* @param {Number} options.normalLayerOpacity 普通图层透明度 (0-1)
* @param {Function} options.onImageGenerated 图片生成回调
* @param {Boolean} options.useBatchMode 是否使用批量模式 (默认true减少闪烁)
* @returns {Promise<boolean>} 是否初始化成功
*/
async initialize(options = {}) {
@@ -70,13 +69,8 @@ export class RedGreenModeManager {
throw new Error("缺少必需的图片URL参数");
}
// 使用批量模式或传统模式
const useBatchMode = options.useBatchMode !== false; // 默认为true
let initCommand;
// 使用新的批量初始化命令,减少页面闪烁
initCommand = new BatchInitializeRedGreenModeCommand({
const initCommand = new BatchInitializeRedGreenModeCommand({
canvas: this.canvas,
layerManager: this.layerManager,
toolManager: this.toolManager,
@@ -94,10 +88,11 @@ export class RedGreenModeManager {
await initCommand.execute();
}
this.registerRedGreenMouseUpEvent();
// 标记为已初始化
this.isInitialized = true;
this.registerRedGreenMouseUpEvent();
// 启用图层管理器的红绿图模式
if (
this.layerManager &&
@@ -106,6 +101,9 @@ export class RedGreenModeManager {
this.layerManager.enableRedGreenMode();
}
// 更新交互性
await this.layerManager?.updateLayersObjectsInteractivity?.();
// 重置工具管理器状态
// 默认红色笔刷
if (this.toolManager) {
@@ -116,7 +114,6 @@ export class RedGreenModeManager {
衣服底图: this.clothingImageUrl,
红绿图: this.redGreenImageUrl,
普通图层透明度: `${Math.round(this.normalLayerOpacity * 100)}%`,
批量模式: useBatchMode ? "已启用" : "已禁用",
画布背景: "白色",
});
@@ -131,17 +128,17 @@ export class RedGreenModeManager {
// 注册鼠标抬起事件
registerRedGreenMouseUpEvent() {
this.canvas.on("mouse:up", (event) => {
if (!this.isInitialized) {
console.warn("红绿图模式未初始化,无法处理鼠标事件");
return;
}
// 可以在这里添加更多逻辑,比如生成图片或更新状态
if (this.onImageGenerated) {
const imageData = this.canvasManager.exportImage();
console.log("生成红绿图图片数据:", imageData);
this.onImageGenerated(imageData);
}
requestAnimationFrame(async () => {
if (!this.isInitialized) {
console.warn("红绿图模式未初始化,无法处理鼠标事件");
return;
}
if (this.onImageGenerated) {
const imageData = await this.canvasManager.exportImage();
this.onImageGenerated(imageData);
}
});
});
}

View File

@@ -3,11 +3,11 @@ import { BrushManager } from "./brushes/brushManager";
import { ToolCommand } from "../commands/ToolCommands";
import { OperationType } from "../utils/layerHelper";
import CanvasConfig from "../config/canvasConfig";
//import { fabric } from "fabric-with-all";
import {
InitLiquifyToolCommand,
RasterizeForLiquifyCommand,
} from "../commands/LiquifyCommands";
import { fabric } from "fabric-with-all";
import { InitLiquifyToolCommand } from "../commands/LiquifyCommands";
import { RasterizeLayerCommand } from "../commands/GroupCommands";
import { message, Modal } from "ant-design-vue";
import { h } from "vue";
/**
* 工具管理器
@@ -378,6 +378,8 @@ export class ToolManager {
previousTool: this.activeTool.value,
});
command.undoable = options.undoable !== undefined ? options.undoable : true;
// 执行命令
this.commandManager.execute(command, { ...options });
}
@@ -690,18 +692,9 @@ export class ToolManager {
}
} else if (checkResult.needsRasterization) {
// 需要栅格化 (多个对象或组)
// 询问用户是否要栅格化
if (
confirm(
checkResult.isGroup
? "组对象需要先栅格化才能进行液化操作,是否立即栅格化?"
: "当前图层含有多个对象,需要先栅格化才能进行液化操作,是否立即栅格化?"
)
) {
// 用户确认栅格化,执行栅格化操作
this._rasterizeLayerForLiquify(activeLayerId);
return; // 栅格化后会重新调用液化功能,这里直接返回
}
// 使用Modal询问用户是否要栅格化
this._showRasterizeConfirmModal(checkResult.isGroup, activeLayerId);
return; // 等待用户确认,不继续执行
}
}
}
@@ -716,51 +709,42 @@ export class ToolManager {
}
/**
* 检查并准备液化操作
* 显示栅格化确认Modal对话框
* @param {Boolean} isGroup 是否为组对象
* @param {String} layerId 图层ID
* @private
*/
_checkAndPrepareForLiquify(layerId) {
// 确保存在液化管理器
const liquifyManager = this.canvasManager?.liquifyManager;
if (!liquifyManager) {
console.error("液化管理器未初始化");
return;
}
_showRasterizeConfirmModal(isGroup, layerId) {
const title = "栅格化图层";
const content = "需要先栅格化才能进行液化操作,是否立即栅格化?";
// 检查图层是否适合液化
const checkResult = liquifyManager.checkLayerForLiquify(layerId);
if (checkResult.isEmpty) {
// 空图层
alert("当前图层为空,无法进行液化操作");
return;
}
if (checkResult.isGroup) {
// 询问是否栅格化组
if (confirm("组对象需要栅格化才能进行液化操作,是否立即栅格化?")) {
Modal.confirm({
title,
content,
okText: "确定栅格化",
cancelText: "取消",
centered: true,
icon: h("span", { style: "color: #faad14;" }, "⚠️"),
onOk: () => {
// 用户确认栅格化,执行栅格化操作
this._rasterizeLayerForLiquify(layerId);
}
return;
}
if (checkResult.needsRasterization) {
// 询问是否栅格化图层
if (
confirm(
"当前图层含有多个对象,需要先栅格化才能进行液化操作,是否立即栅格化?"
)
) {
this._rasterizeLayerForLiquify(layerId);
}
return;
}
// 如果图层可以直接液化(单个图像对象)
if (checkResult.valid) {
this._startLiquify(layerId);
}
},
onCancel: () => {
console.log("用户取消了栅格化操作");
// 用户取消,触发液化面板显示事件但不能液化
document.dispatchEvent(
new CustomEvent("showLiquifyPanel", {
detail: {
activeLayerId: layerId,
layerStatus: { needsRasterization: true, isGroup },
canLiquify: false,
targetObject: null,
originalImageData: null,
},
})
);
},
});
}
/**
@@ -772,25 +756,52 @@ export class ToolManager {
if (!this.commandManager || !this.layerManager) return;
try {
// 导入液化相关命令
// 显示加载Modal
const loadingModal = Modal.info({
title: "正在栅格化",
content: "正在栅格化图层,请稍候...",
okButtonProps: { style: { display: "none" } },
centered: true,
closable: false,
maskClosable: false,
});
// 创建栅格化命令
const rasterizeCommand = new RasterizeForLiquifyCommand({
const rasterizeCommand = new RasterizeLayerCommand({
canvas: this.canvas,
layerManager: this.layerManager,
layerId: layerId,
layers: this.layerManager.layers,
activeLayerId: this.layerManager.activeLayerId,
});
// 执行命令
const result = await this.commandManager.execute(rasterizeCommand);
// 关闭加载Modal
loadingModal.destroy();
if (result) {
// 栅格化成功,启动液化
message.success("图层已成功栅格化,可以进行液化操作");
this._startLiquify(layerId);
} else {
// 栅格化失败
Modal.error({
title: "栅格化失败",
content: "栅格化失败,无法进行液化操作",
okText: "确定",
centered: true,
});
}
} catch (error) {
console.error("栅格化图层失败:", error);
alert("栅格化失败,无法进行液化操作");
Modal.error({
title: "栅格化错误",
content: `栅格化失败:${error.message}`,
okText: "确定",
centered: true,
});
}
}
@@ -803,7 +814,12 @@ export class ToolManager {
// 获取图层信息
const layer = this.layerManager.getLayerById(layerId);
if (!layer) {
console.error("图层不存在");
Modal.error({
title: "图层错误",
content: "图层不存在",
okText: "确定",
centered: true,
});
return;
}
@@ -812,14 +828,24 @@ export class ToolManager {
if (layer.isBackground) {
// 背景图层使用 fabricObject (单数)
if (!layer.fabricObject) {
console.error("背景图层为空");
Modal.warning({
title: "背景图层为空",
content: "背景图层为空,无法进行液化操作",
okText: "确定",
centered: true,
});
return;
}
targetObject = layer.fabricObject;
} else {
// 普通图层使用 fabricObjects (复数)
if (!layer.fabricObjects || layer.fabricObjects.length === 0) {
console.error("图层为空");
Modal.warning({
title: "图层为空",
content: "图层为空,无法进行液化操作",
okText: "确定",
centered: true,
});
return;
}
targetObject = layer.fabricObjects[0];
@@ -828,11 +854,26 @@ export class ToolManager {
// 确保liquifyManager可用
const liquifyManager = this.canvasManager?.liquifyManager;
if (!liquifyManager) {
console.error("液化管理器未初始化");
Modal.error({
title: "液化管理器错误",
content: "液化管理器未初始化",
okText: "确定",
centered: true,
});
return;
}
try {
// 显示准备中的Modal
const preparingModal = Modal.info({
title: "准备液化环境",
content: "正在准备液化环境,请稍候...",
okButtonProps: { style: { display: "none" } },
centered: true,
closable: false,
maskClosable: false,
});
// 准备液化环境
liquifyManager.initialize({
canvas: this.canvas,
@@ -855,6 +896,9 @@ export class ToolManager {
// 执行初始化命令
await this.commandManager.execute(initCommand);
// 关闭准备Modal
preparingModal.destroy();
// 触发液化面板显示事件
document.dispatchEvent(
new CustomEvent("showLiquifyPanel", {
@@ -867,7 +911,12 @@ export class ToolManager {
);
} catch (error) {
console.error("启动液化工具失败:", error);
alert("启动液化工具失败:" + error.message);
Modal.error({
title: "液化工具启动失败",
content: `启动液化工具失败:${error.message}`,
okText: "确定",
centered: true,
});
}
}

View File

@@ -577,6 +577,49 @@ export class TexturePresetManager {
cacheSize: this.textureCache.size,
};
}
/**
* 验证纹理文件
* @param {File} file 要验证的文件
* @returns {Boolean} 是否为有效的纹理文件
*/
validateTextureFile(file) {
if (!file) {
console.warn("文件不存在");
return false;
}
// 检查文件类型
if (!file.type.startsWith("image/")) {
console.warn("文件类型无效,必须是图片文件");
return false;
}
// 检查文件大小(限制为 10MB
const maxSize = 10 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
console.warn("文件大小超过限制10MB");
return false;
}
// 检查支持的图片格式
const supportedTypes = [
"image/jpeg",
"image/jpg",
"image/png",
"image/webp",
"image/svg+xml",
"image/bmp",
"image/gif",
];
if (!supportedTypes.includes(file.type)) {
console.warn("不支持的图片格式");
return false;
}
return true;
}
}
// 创建单例实例

View File

@@ -1,4 +1,5 @@
//import { fabric } from "fabric-with-all";
import { fabric } from "fabric-with-all";
import "./fabric.brushes.js";
import { BrushStore } from "../../store/BrushStore";
import { brushRegistry } from "./BrushRegistry";
@@ -16,7 +17,7 @@ import { MarkerBrush } from "./types/MarkerBrush";
import { CustomPenBrush } from "./types/CustomPenBrush";
import { RibbonBrush } from "./types/RibbonBrush";
import { ShadedBrush } from "./types/ShadedBrush";
import { SketchyBrush } from "./types/SketchyBrush";
// import { SketchyBrush } from "./types/SketchyBrush";
import { SpraypaintBrush } from "./types/SpraypaintBrush";
/**
@@ -42,6 +43,11 @@ export class BrushManager {
// 初始化笔刷注册
this._registerDefaultBrushes();
// 初始化橡皮擦状态管理器
this.eraserStateManager = null;
this.isErasingActive = false;
this.currentErasedObjects = []; // 当前擦除会话中被影响的对象
}
/**
@@ -51,64 +57,112 @@ export class BrushManager {
_registerDefaultBrushes() {
// 注册铅笔笔刷
brushRegistry.register("pencil", PencilBrush, {
name: "铅笔",
name: "Pencil",
description: "基础铅笔工具,适合精细线条绘制",
category: "基础笔刷",
});
// 注册材质笔刷
brushRegistry.register("texture", TextureBrush);
brushRegistry.register("texture", TextureBrush, {
name: "Texture",
description: "使用纹理图片作为笔刷,支持缩放和透明度",
category: "基础笔刷",
});
// 注册集成的笔刷类型
brushRegistry.register("crayon", CrayonBrush);
brushRegistry.register("fur", FurBrush);
brushRegistry.register("ink", InkBrush);
brushRegistry.register("longfur", LongfurBrush);
brushRegistry.register("writing", WritingBrush);
brushRegistry.register("marker", MarkerBrush);
brushRegistry.register("pen", CustomPenBrush);
brushRegistry.register("ribbon", RibbonBrush);
brushRegistry.register("shaded", ShadedBrush);
brushRegistry.register("sketchy", SketchyBrush);
brushRegistry.register("spraypaint", SpraypaintBrush);
brushRegistry.register("crayon", CrayonBrush, {
name: "Crayon",
description: "使用纹理图片作为笔刷,支持缩放和透明度",
category: "基础笔刷",
});
brushRegistry.register("fur", FurBrush, {
name: "Texture",
description: "使用纹理图片作为笔刷,支持缩放和透明度",
category: "基础笔刷",
});
brushRegistry.register("ink", InkBrush, {
name: "Ink",
description: "墨水笔刷,适合书写和绘图",
category: "基础笔刷",
});
brushRegistry.register("", LongfurBrush, {
name: "Longfur",
description: "长毛发笔刷,适合绘制动物毛皮、草或头发",
category: "基础笔刷",
});
brushRegistry.register("writing", WritingBrush, {
name: "Writing",
description: "书法笔刷,模拟中国传统书法毛笔效果,具有笔锋和墨色变化",
category: "基础笔刷",
});
brushRegistry.register("marker", MarkerBrush, {
name: "Marker",
description: "马克笔笔刷,适合粗线条和填充",
category: "基础笔刷",
});
brushRegistry.register("pen", CustomPenBrush, {
name: "Pen",
description: "自定义钢笔笔刷,适合书写和绘图",
category: "基础笔刷",
});
brushRegistry.register("ribbon", RibbonBrush, {
name: "Ribbon",
description: "丝带笔刷,适合创建流动的丝带效果",
category: "基础笔刷",
});
brushRegistry.register("shaded", ShadedBrush, {
name: "Shaded",
description: "阴影笔刷,适合创建渐变和阴影效果",
category: "基础笔刷",
});
// brushRegistry.register("sketchy", SketchyBrush);
brushRegistry.register("spraypaint", SpraypaintBrush, {
name: "Spraypaint",
description: "喷漆笔刷,模拟喷漆效果",
category: "基础笔刷",
});
// 注册喷枪笔刷
brushRegistry.register(
"spray",
class SprayBrush extends PencilBrush {
constructor(canvas, options = {}) {
super(canvas, {
id: "spray",
name: "喷枪",
description: "模拟喷枪效果,创建散点效果",
category: "基础笔刷",
...options,
});
}
// // 注册喷枪笔刷
// brushRegistry.register(
// "spray",
// class SprayBrush extends PencilBrush {
// constructor(canvas, options = {}) {
// super(canvas, {
// id: "spray",
// name: "喷枪",
// description: "模拟喷枪效果,创建散点效果",
// category: "基础笔刷",
// ...options,
// });
// }
create() {
this.brush = new fabric.SprayBrush(this.canvas);
this.configure(this.brush, this.options);
return this.brush;
}
// create() {
// this.brush = new fabric.SprayBrush(this.canvas);
// this.configure(this.brush, this.options);
// return this.brush;
// }
configure(brush, options = {}) {
super.configure(brush, options);
// configure(brush, options = {}) {
// super.configure(brush, options);
if (options.density !== undefined) {
brush.density = options.density;
}
// if (options.density !== undefined) {
// brush.density = options.density;
// }
if (options.randomOpacity !== undefined) {
brush.randomOpacity = options.randomOpacity;
}
// if (options.randomOpacity !== undefined) {
// brush.randomOpacity = options.randomOpacity;
// }
if (options.dotWidth !== undefined) {
brush.dotWidth = options.dotWidth;
}
}
}
);
// if (options.dotWidth !== undefined) {
// brush.dotWidth = options.dotWidth;
// }
// }
// },
// {
// name: "喷枪",
// description: "模拟喷枪效果,创建散点效果",
// }
// );
// 注册橡皮擦笔刷
brushRegistry.register(
"eraser",
@@ -187,87 +241,87 @@ export class BrushManager {
}
);
// 注册水彩笔刷
brushRegistry.register(
"watercolor",
class WatercolorBrush extends PencilBrush {
constructor(canvas, options = {}) {
super(canvas, {
id: "watercolor",
name: "水彩",
description: "模拟水彩效果,带有流动感和透明感",
category: "特效笔刷",
...options,
});
}
// // 注册水彩笔刷
// brushRegistry.register(
// "watercolor",
// class WatercolorBrush extends PencilBrush {
// constructor(canvas, options = {}) {
// super(canvas, {
// id: "watercolor",
// name: "水彩",
// description: "模拟水彩效果,带有流动感和透明感",
// category: "特效笔刷",
// ...options,
// });
// }
create() {
// 创建一个自定义的PencilBrush来模拟水彩效果
this.brush = new fabric.PencilBrush(this.canvas);
this.configure(this.brush, this.options);
// create() {
// // 创建一个自定义的PencilBrush来模拟水彩效果
// this.brush = new fabric.PencilBrush(this.canvas);
// this.configure(this.brush, this.options);
// 水彩效果特有的属性
this.brush.globalCompositeOperation = "multiply";
this.brush.shadow = new fabric.Shadow({
color: this.options.color || "#000",
blur: 5,
offsetX: 0,
offsetY: 0,
});
// // 水彩效果特有的属性
// this.brush.globalCompositeOperation = "multiply";
// this.brush.shadow = new fabric.Shadow({
// color: this.options.color || "#000",
// blur: 5,
// offsetX: 0,
// offsetY: 0,
// });
return this.brush;
}
// return this.brush;
// }
configure(brush, options = {}) {
super.configure(brush, options);
// configure(brush, options = {}) {
// super.configure(brush, options);
// 水彩笔刷特有的配置
brush.opacity = Math.min(0.5, options.opacity || 0.3); // 默认透明度30%
}
}
);
// // 水彩笔刷特有的配置
// brush.opacity = Math.min(0.5, options.opacity || 0.3); // 默认透明度30%
// }
// }
// );
// 注册粉笔笔刷
brushRegistry.register(
"chalk",
class ChalkBrush extends PencilBrush {
constructor(canvas, options = {}) {
super(canvas, {
id: "chalk",
name: "粉笔",
description: "模拟粉笔效果,有颗粒感和不连续性",
category: "特效笔刷",
...options,
});
}
// // 注册粉笔笔刷
// brushRegistry.register(
// "chalk",
// class ChalkBrush extends PencilBrush {
// constructor(canvas, options = {}) {
// super(canvas, {
// id: "chalk",
// name: "粉笔",
// description: "模拟粉笔效果,有颗粒感和不连续性",
// category: "特效笔刷",
// ...options,
// });
// }
create() {
this.brush = new fabric.PencilBrush(this.canvas);
this.configure(this.brush, this.options);
// create() {
// this.brush = new fabric.PencilBrush(this.canvas);
// this.configure(this.brush, this.options);
// 自定义绘画方法来模拟粉笔效果
const originalOnMouseMove = this.brush.onMouseMove;
this.brush.onMouseMove = function (pointer, options) {
// 随机调整坐标位置,增加粉笔质感
const jitter = 2;
pointer.x += (Math.random() - 0.5) * jitter;
pointer.y += (Math.random() - 0.5) * jitter;
// // 自定义绘画方法来模拟粉笔效果
// const originalOnMouseMove = this.brush.onMouseMove;
// this.brush.onMouseMove = function (pointer, options) {
// // 随机调整坐标位置,增加粉笔质感
// const jitter = 2;
// pointer.x += (Math.random() - 0.5) * jitter;
// pointer.y += (Math.random() - 0.5) * jitter;
// 调用原始方法
originalOnMouseMove.call(this, pointer, options);
};
// // 调用原始方法
// originalOnMouseMove.call(this, pointer, options);
// };
return this.brush;
}
// return this.brush;
// }
configure(brush, options = {}) {
super.configure(brush, options);
// configure(brush, options = {}) {
// super.configure(brush, options);
// 粉笔特有的设置
brush.strokeDashArray = [5, 5]; // 虚线效果
}
}
);
// // 粉笔特有的设置
// brush.strokeDashArray = [5, 5]; // 虚线效果
// }
// }
// );
}
/**
@@ -571,6 +625,148 @@ export class BrushManager {
return success;
}
/**
* 设置依赖管理器
*/
setManagers({ layerManager, commandManager }) {
this.layerManager = layerManager;
this.commandManager = commandManager;
// 初始化橡皮擦状态管理器
if (this.canvas && this.layerManager) {
this.eraserStateManager = new EraserStateManager(
this.canvas,
this.layerManager
);
}
}
/**
* 创建橡皮擦
* @param {Object} options 橡皮擦选项
*/
createEraser(options = {}) {
if (!this.canvas) {
console.error("画布未初始化");
return null;
}
try {
// 直接使用 fabric-with-erasing 库提供的 EraserBrush
this.brush = new fabric.EraserBrush(this.canvas);
// 应用配置
this.configure(this.brush, {
width: this.brushSize.value,
color: this.brushColor.value,
opacity: this.brushOpacity.value,
inverted: options.inverted || false,
...options,
});
// 设置画布为绘图模式
this.canvas.isDrawingMode = true;
this.canvas.freeDrawingBrush = this.brush;
// 绑定橡皮擦事件处理器
// this._bindEraserEvents();
console.log("橡皮擦创建成功");
return this.brush;
} catch (error) {
console.error("创建橡皮擦失败:", error);
return null;
}
}
/**
* 绑定橡皮擦事件处理器
* @private
*/
_bindEraserEvents() {
if (!this.canvas || !this.eraserStateManager) return;
// 监听擦除开始事件
this.canvas.on("erasing:start", this._handleErasingStart.bind(this));
// 监听擦除结束事件
this.canvas.on("erasing:end", this._handleErasingEnd.bind(this));
}
/**
* 解绑橡皮擦事件处理器
* @private
*/
_unbindEraserEvents() {
if (!this.canvas) return;
this.canvas.off("erasing:start", this._handleErasingStart.bind(this));
this.canvas.off("erasing:end", this._handleErasingEnd.bind(this));
}
/**
* 处理擦除开始事件
* @param {Object} e 事件对象
* @private
*/
_handleErasingStart(e) {
console.log("橡皮擦开始擦除");
if (!this.eraserStateManager) return;
// 标记擦除状态
this.isErasingActive = true;
this.currentErasedObjects = [];
// 捕获擦除前的状态
this.eraserStateManager.startErasing();
}
/**
* 处理擦除结束事件
* @param {Object} e 事件对象
* @private
*/
_handleErasingEnd(e) {
console.log("橡皮擦擦除结束", e);
if (!this.eraserStateManager || !this.isErasingActive) return;
// 获取被擦除的对象
const affectedObjects = e.targets || [];
// 创建橡皮擦命令
const eraserCommand = this.eraserStateManager.endErasing(affectedObjects);
// 如果有有效的命令且有命令管理器,执行命令
if (eraserCommand && this.commandManager) {
// 注意:不需要调用 execute(),因为擦除操作已经完成
// 只需要将命令添加到历史记录中以支持撤销
this.commandManager.addToHistory(eraserCommand);
console.log("橡皮擦操作已添加到命令历史");
}
// 重置状态
this.isErasingActive = false;
this.currentErasedObjects = [];
}
/**
* 取消当前擦除操作
*/
cancelErasing() {
if (!this.isErasingActive) return;
if (this.eraserStateManager) {
this.eraserStateManager.cancelErasing();
}
this.isErasingActive = false;
this.currentErasedObjects = [];
console.log("当前擦除操作已取消");
}
/**
* 获取当前笔刷类型
* @returns {String} 当前笔刷类型ID
@@ -646,7 +842,6 @@ export class BrushManager {
return this.canvas.freeDrawingBrush;
}
/**
* 创建橡皮擦
* @returns {Object} 橡皮擦笔刷
@@ -703,9 +898,20 @@ export class BrushManager {
}
/**
* 销毁资源
* 销毁笔刷管理器
*/
dispose() {
// 解绑事件
// this._unbindEraserEvents();
// 取消进行中的擦除操作
this.cancelErasing();
// 清理状态管理器
if (this.eraserStateManager) {
this.eraserStateManager = null;
}
// 销毁当前笔刷
if (this.activeBrush) {
this.activeBrush.destroy();

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
import { BaseBrush } from "../BaseBrush";
//import { fabric } from "fabric-with-all";
import { fabric } from "fabric-with-all";
import texturePresetManager from "../TexturePresetManager";
/**
@@ -71,7 +71,72 @@ export class TextureBrush extends BaseBrush {
// 创建fabric原生纹理笔刷
this.brush = new fabric.PatternBrush(this.canvas);
// 配置笔刷
// 重写 _finalizeAndAddPath 方法,使其调用 convertToImg 而不是创建 Path 对象
const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind(
this.brush
);
const self = this; // 保存外部this引用
this.brush._finalizeAndAddPath = function () {
console.log("TextureBrush: _finalizeAndAddPath called");
const ctx = this.canvas.contextTop;
ctx.closePath();
// 应用点简化如果PatternBrush支持
if (this.decimate && this._points) {
this._points = this.decimatePoints(this._points, this.decimate);
}
console.log(
"TextureBrush: points count =",
this._points ? this._points.length : 0
);
// 检查是否有有效的路径数据
if (!this._points || this._points.length < 2) {
// 如果点数不足,直接请求重新渲染
console.log("TextureBrush: Not enough points, skipping");
this.canvas.requestRenderAll();
return;
}
const pathData = this.convertPointsToSVGPath(this._points);
const isEmpty = self._isEmptySVGPath(pathData);
console.log("TextureBrush: isEmpty =", isEmpty);
if (isEmpty) {
// 如果路径为空,直接请求重新渲染
console.log("TextureBrush: Path is empty, skipping");
this.canvas.requestRenderAll();
return;
}
// 先触发事件,模拟原生行为
const path = this.createPath(pathData);
this.canvas.fire("before:path:created", { path: path });
console.log("TextureBrush: Calling convertToImg");
// 调用 convertToImg 方法将绘制内容转换为图片
if (typeof this.convertToImg === "function") {
this.convertToImg();
console.log("TextureBrush: convertToImg called successfully");
} else {
console.warn(
"convertToImg method not found, falling back to original behavior"
);
// 如果没有convertToImg方法回退到原始行为
this.canvas.add(path);
this.canvas.fire("path:created", { path: path });
this.canvas.clearContext(this.canvas.contextTop);
}
// 重置阴影
this._resetShadow();
};
// 配置笔刷基本属性
this.configure(this.brush, this.options);
// 如果有选中的材质,则设置纹理
@@ -221,6 +286,11 @@ export class TextureBrush extends BaseBrush {
canvasTexture.width = width;
canvasTexture.height = height;
// 应用透明度设置
if (this.textureOpacity < 1) {
ctx.globalAlpha = this.textureOpacity;
}
// 绘制前应用旋转
if (this.textureAngle !== 0) {
ctx.save();
@@ -233,26 +303,16 @@ export class TextureBrush extends BaseBrush {
ctx.drawImage(img, 0, 0, width, height);
}
// 应用透明度
if (this.textureOpacity < 1) {
ctx.globalAlpha = this.textureOpacity;
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height);
}
// 缓存处理后的纹理图像
this._processedTextureCanvas = canvasTexture;
// 创建Pattern对象
const pattern = new fabric.Pattern({
source: canvasTexture,
repeat: this.textureRepeat,
});
// 使用fabric.js 5.x正确的API方式设置PatternBrush
// 直接设置source属性这是PatternBrush的标准用法
this.brush.source = canvasTexture;
// 设置笔刷源纹理
if (typeof this.brush.setSource === "function") {
this.brush.setSource(pattern);
} else if (typeof this.brush.source === "object") {
this.brush.source = pattern;
} else if (typeof this.brush.pattern === "object") {
this.brush.pattern = pattern;
// 通知画布重绘
if (this.canvas && this.canvas.requestRenderAll) {
this.canvas.requestRenderAll();
}
}
@@ -852,4 +912,37 @@ export class TextureBrush extends BaseBrush {
getTextureStats() {
return texturePresetManager.getStats();
}
/**
* 检查 SVG 路径是否为空
* @private
* @param {Array} pathData SVG 路径数据
* @returns {Boolean} 是否为空路径
*/
_isEmptySVGPath(pathData) {
if (!pathData || pathData.length === 0) {
return true;
}
// 检查路径是否只包含移动命令或者是一个点
let hasDrawing = false;
let moveCount = 0;
for (let i = 0; i < pathData.length; i++) {
const command = pathData[i];
if (command[0] === "M") {
moveCount++;
} else if (
command[0] === "L" ||
command[0] === "Q" ||
command[0] === "C"
) {
hasDrawing = true;
break;
}
}
// 如果只有移动命令且超过1个或者没有绘制命令则认为是空路径
return !hasDrawing || (moveCount > 0 && pathData.length <= moveCount);
}
}

View File

@@ -576,7 +576,7 @@ export class CanvasEventManager {
}
/**
* 合并图层中的对象为图像以提高性能
* 合并图层中的对象为以提高性能
* @param {Object} options 合并选项
* @param {fabric.Image} options.fabricImage 新的图像对象
* @param {Object} options.activeLayer 当前活动图层
@@ -594,7 +594,6 @@ export class CanvasEventManager {
console.warn("合并对象失败:没有活动图层");
return;
}
// 验证是否需要合并
const hasExistingObjects =
Array.isArray(activeLayer.fabricObjects) &&

View File

@@ -4,6 +4,7 @@
*/
import { LiquifyWebGLManager } from "./LiquifyWebGLManager";
import { LiquifyCPUManager } from "./LiquifyCPUManager";
import { LayerType } from "../../utils/layerHelper";
export class EnhancedLiquifyManager {
/**
@@ -313,16 +314,40 @@ export class EnhancedLiquifyManager {
if (param in this.params) {
this.params[param] = value;
// 同步更新当前渲染器
if (this.activeRenderer) {
// 同步更新当前渲染器 - 关键修复:确保参数正确传递
if (
this.activeRenderer &&
typeof this.activeRenderer.setParam === "function"
) {
console.log(`EnhancedLiquifyManager 设置参数: ${param}=${value}`);
this.activeRenderer.setParam(param, value);
} else {
console.warn(
`EnhancedLiquifyManager: 无法设置参数 ${param},渲染器未就绪`
);
}
return true;
}
console.warn(`EnhancedLiquifyManager: 无效参数 ${param}`);
return false;
}
/**
* 批量设置参数
* @param {Object} params 参数对象
*/
setParams(params) {
console.log("EnhancedLiquifyManager 批量设置参数:", params);
if (params && typeof params === "object") {
Object.entries(params).forEach(([key, value]) => {
this.setParam(key, value);
});
}
}
/**
* 获取当前参数
* @returns {Object} 当前参数对象
@@ -348,6 +373,36 @@ export class EnhancedLiquifyManager {
}
}
/**
* 开始液化操作(记录初始点)
* @param {Number} x 初始X坐标
* @param {Number} y 初始Y坐标
*/
startLiquifyOperation(x, y) {
if (
this.activeRenderer &&
typeof this.activeRenderer.startDeformation === "function"
) {
this.activeRenderer.startDeformation(x, y);
}
console.log(
`开始液化操作,渲染模式=${this.renderMode}, 初始点: (${x}, ${y})`
);
}
/**
* 结束液化操作
*/
endLiquifyOperation() {
if (
this.activeRenderer &&
typeof this.activeRenderer.endDeformation === "function"
) {
this.activeRenderer.endDeformation();
}
console.log(`结束液化操作,渲染模式=${this.renderMode}`);
}
/**
* 应用液化变形
* @param {Object} target 目标对象
@@ -468,7 +523,9 @@ export class EnhancedLiquifyManager {
// 注意:这里不自动切换,因为可能会导致中途渲染结果不一致
}
}
setRealtimeUpdater(realtimeUpdater) {
this.realtimeUpdater = realtimeUpdater;
}
/**
* 重置液化操作
* @returns {ImageData} 重置后的图像数据
@@ -519,7 +576,7 @@ export class EnhancedLiquifyManager {
// 检查图层是否为空
let objectsToCheck = [];
if (layer.isBackground || layer.type === "background") {
if (layer.isBackground || layer.type === "background" || layer.isFixed) {
// 背景图层使用 fabricObject (单数)
if (layer.fabricObject) {
objectsToCheck = [layer.fabricObject];
@@ -548,7 +605,10 @@ export class EnhancedLiquifyManager {
objectsToCheck[0].type === "rasterized-layer");
// 检查是否为组
const isGroup = objectsToCheck.some((obj) => obj.type === "group");
const isGroup =
objectsToCheck.some((obj) => obj.type === "group") ||
layer.type === LayerType.GROUP ||
layer.children?.length > 0;
// 如果不是单一图像,需要栅格化
const needsRasterization = !isImage || isGroup;
@@ -572,26 +632,34 @@ export class EnhancedLiquifyManager {
async _getImageData(fabricObject) {
return new Promise((resolve, reject) => {
try {
// 创建临时canvas
// 创建临时canvas - 关键修复使用原始图像尺寸不考虑fabric对象的缩放
const tempCanvas = document.createElement("canvas");
tempCanvas.width = fabricObject.width * fabricObject.scaleX;
tempCanvas.height = fabricObject.height * fabricObject.scaleY;
// 使用图像的原始尺寸,而不是缩放后的尺寸
tempCanvas.width = fabricObject.width;
tempCanvas.height = fabricObject.height;
const tempCtx = tempCanvas.getContext("2d");
// 如果对象有图像元素
if (fabricObject._element) {
// 绘制原始尺寸的图像
tempCtx.drawImage(
fabricObject._element,
0,
0,
tempCanvas.width,
tempCanvas.height
fabricObject.width,
fabricObject.height
);
} else if (fabricObject.getSrc) {
// 通过URL创建图像
const img = new Image();
img.onload = () => {
tempCtx.drawImage(img, 0, 0, tempCanvas.width, tempCanvas.height);
tempCtx.drawImage(
img,
0,
0,
fabricObject.width,
fabricObject.height
);
const imageData = tempCtx.getImageData(
0,
0,
@@ -615,6 +683,13 @@ export class EnhancedLiquifyManager {
tempCanvas.width,
tempCanvas.height
);
console.log(
`获取图像数据: 对象尺寸=${fabricObject.width}x${fabricObject.height}, ` +
`对象缩放=(${fabricObject.scaleX}, ${fabricObject.scaleY}), ` +
`图像数据尺寸=${imageData.width}x${imageData.height}`
);
resolve(imageData);
} catch (error) {
reject(error);

View File

@@ -0,0 +1,451 @@
/**
* 混合液化管理器 - 根据模式智能选择算法
*/
export class HybridLiquifyManager {
constructor(options = {}) {
this.config = {
gridSize: options.gridSize || 16,
maxStrength: options.maxStrength || 200,
smoothingIterations: options.smoothingIterations || 1,
relaxFactor: options.relaxFactor || 0.1,
};
this.params = {
size: 80,
pressure: 0.8,
distortion: 0,
power: 0.8,
};
this.modes = {
PUSH: "push",
CLOCKWISE: "clockwise",
COUNTERCLOCKWISE: "counterclockwise",
PINCH: "pinch",
EXPAND: "expand",
CRYSTAL: "crystal",
EDGE: "edge",
RECONSTRUCT: "reconstruct",
};
// 定义哪些模式使用像素算法
this.pixelModes = new Set([
this.modes.CLOCKWISE,
this.modes.COUNTERCLOCKWISE,
this.modes.CRYSTAL,
this.modes.EDGE,
]);
// 定义哪些模式使用网格算法
this.meshModes = new Set([
this.modes.PUSH,
this.modes.PINCH,
this.modes.EXPAND,
this.modes.RECONSTRUCT,
]);
this.currentMode = this.modes.PUSH;
this.originalImageData = null;
this.currentImageData = null;
this.mesh = null;
this.initialized = false;
this.canvas = document.createElement("canvas");
this.ctx = this.canvas.getContext("2d");
// 持续按压相关状态
this.pressStartTime = 0;
this.pressDuration = 0;
this.accumulatedRotation = 0;
this.accumulatedScale = 0;
this.isHolding = false;
this.continuousTimer = null;
// 鼠标状态
this.initialMouseX = 0;
this.initialMouseY = 0;
this.currentMouseX = 0;
this.currentMouseY = 0;
this.isDragging = false;
this.dragDistance = 0;
}
// ...existing initialization methods...
/**
* 应用液化变形 - 智能选择算法
*/
applyDeformation(x, y) {
if (!this.initialized || !this.originalImageData) {
return this.currentImageData;
}
// 更新鼠标位置
this.currentMouseX = x;
this.currentMouseY = y;
const { size, pressure, power } = this.params;
const radius = size;
const strength = pressure * power;
// 根据模式选择算法
if (this.pixelModes.has(this.currentMode)) {
return this._applyPixelDeformation(x, y, radius, strength);
} else if (this.meshModes.has(this.currentMode)) {
return this._applyMeshDeformation(x, y, radius, strength);
}
return this.currentImageData;
}
/**
* 像素级液化算法 - 适用于旋转、水晶、边缘模式
*/
_applyPixelDeformation(centerX, centerY, radius, strength) {
const data = this.currentImageData.data;
const width = this.currentImageData.width;
const height = this.currentImageData.height;
const tempData = new Uint8ClampedArray(data);
const processRadius = Math.min(radius, Math.min(width, height) / 2);
// 计算影响区域边界
const minX = Math.max(0, Math.floor(centerX - processRadius));
const maxX = Math.min(width, Math.ceil(centerX + processRadius));
const minY = Math.max(0, Math.floor(centerY - processRadius));
const maxY = Math.min(height, Math.ceil(centerY + processRadius));
switch (this.currentMode) {
case this.modes.CLOCKWISE:
case this.modes.COUNTERCLOCKWISE:
this._applyPixelRotation(
tempData,
data,
width,
height,
centerX,
centerY,
processRadius,
strength,
this.currentMode === this.modes.CLOCKWISE
);
break;
case this.modes.CRYSTAL:
this._applyPixelCrystal(
tempData,
data,
width,
height,
centerX,
centerY,
processRadius,
strength
);
break;
case this.modes.EDGE:
this._applyPixelEdge(
tempData,
data,
width,
height,
centerX,
centerY,
processRadius,
strength
);
break;
}
return this.currentImageData;
}
/**
* 像素级旋转算法
*/
_applyPixelRotation(
srcData,
dstData,
width,
height,
centerX,
centerY,
radius,
strength,
clockwise
) {
// 计算旋转角度
const timeFactor = Math.min(this.pressDuration / 1000, 5.0);
const baseRotationSpeed = 0.015;
const rotationAngle =
(clockwise ? 1 : -1) *
baseRotationSpeed *
strength *
(1.0 + timeFactor * 0.3);
this.accumulatedRotation += rotationAngle;
const minX = Math.max(0, Math.floor(centerX - radius));
const maxX = Math.min(width, Math.ceil(centerX + radius));
const minY = Math.max(0, Math.floor(centerY - radius));
const maxY = Math.min(height, Math.ceil(centerY + radius));
for (let y = minY; y < maxY; y++) {
for (let x = minX; x < maxX; x++) {
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < radius && distance > 0.1) {
// 距离衰减:内圈快,外圈慢
const normalizedDistance = distance / radius;
const falloff = Math.pow(1 - normalizedDistance, 2); // 二次衰减
// 计算旋转后的源位置
const angle = Math.atan2(dy, dx);
const newAngle = angle + this.accumulatedRotation * falloff;
const sourceX = centerX + Math.cos(newAngle) * distance;
const sourceY = centerY + Math.sin(newAngle) * distance;
// 双线性插值采样
const color = this._bilinearSample(
srcData,
width,
height,
sourceX,
sourceY
);
if (color) {
const targetIdx = (y * width + x) * 4;
dstData[targetIdx] = color[0];
dstData[targetIdx + 1] = color[1];
dstData[targetIdx + 2] = color[2];
dstData[targetIdx + 3] = color[3];
}
}
}
}
}
/**
* 像素级水晶效果
*/
_applyPixelCrystal(
srcData,
dstData,
width,
height,
centerX,
centerY,
radius,
strength
) {
const timeFactor = Math.min(this.pressDuration / 1000, 3.0);
const distortionStrength = strength * (1.0 + timeFactor * 0.5);
const minX = Math.max(0, Math.floor(centerX - radius));
const maxX = Math.min(width, Math.ceil(centerX + radius));
const minY = Math.max(0, Math.floor(centerY - radius));
const maxY = Math.min(height, Math.ceil(centerY + radius));
for (let y = minY; y < maxY; y++) {
for (let x = minX; x < maxX; x++) {
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < radius && distance > 0.1) {
const normalizedDistance = distance / radius;
const falloff = 1 - normalizedDistance * normalizedDistance;
const angle = Math.atan2(dy, dx);
const crystalRadius = normalizedDistance;
// 多层波浪扭曲
const wave1 = Math.sin(angle * 8 + this.pressDuration * 0.005) * 0.6;
const wave2 = Math.cos(angle * 12 + this.pressDuration * 0.003) * 0.4;
const waveAngle =
angle + (wave1 + wave2) * distortionStrength * falloff;
const radialMod =
1 +
Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) *
0.3;
const modDistance = distance * radialMod;
const sourceX = centerX + Math.cos(waveAngle) * modDistance;
const sourceY = centerY + Math.sin(waveAngle) * modDistance;
const color = this._bilinearSample(
srcData,
width,
height,
sourceX,
sourceY
);
if (color) {
const targetIdx = (y * width + x) * 4;
const factor = falloff * distortionStrength * 0.7;
// 混合原始颜色和扭曲颜色
const originalIdx = (y * width + x) * 4;
dstData[targetIdx] = Math.round(
srcData[originalIdx] * (1 - factor) + color[0] * factor
);
dstData[targetIdx + 1] = Math.round(
srcData[originalIdx + 1] * (1 - factor) + color[1] * factor
);
dstData[targetIdx + 2] = Math.round(
srcData[originalIdx + 2] * (1 - factor) + color[2] * factor
);
dstData[targetIdx + 3] = srcData[originalIdx + 3];
}
}
}
}
}
/**
* 像素级边缘效果
*/
_applyPixelEdge(
srcData,
dstData,
width,
height,
centerX,
centerY,
radius,
strength
) {
const timeFactor = Math.min(this.pressDuration / 1000, 2.5);
const edgeStrength = strength * (1.0 + timeFactor * 0.4);
const minX = Math.max(0, Math.floor(centerX - radius));
const maxX = Math.min(width, Math.ceil(centerX + radius));
const minY = Math.max(0, Math.floor(centerY - radius));
const maxY = Math.min(height, Math.ceil(centerY + radius));
for (let y = minY; y < maxY; y++) {
for (let x = minX; x < maxX; x++) {
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < radius && distance > 0.1) {
const normalizedDistance = distance / radius;
const falloff = 1 - normalizedDistance * normalizedDistance;
const angle = Math.atan2(dy, dx);
const edgeRadius = normalizedDistance;
const edgeWave =
Math.sin(edgeRadius * Math.PI * 4 + this.pressDuration * 0.004) *
Math.cos(angle * 6 + this.pressDuration * 0.002);
const perpAngle = angle + Math.PI / 2;
const edgeFactor = edgeWave * falloff * edgeStrength * 0.5;
const offsetX = Math.cos(perpAngle) * edgeFactor;
const offsetY = Math.sin(perpAngle) * edgeFactor;
const sourceX = x + offsetX;
const sourceY = y + offsetY;
const color = this._bilinearSample(
srcData,
width,
height,
sourceX,
sourceY
);
if (color) {
const targetIdx = (y * width + x) * 4;
dstData[targetIdx] = color[0];
dstData[targetIdx + 1] = color[1];
dstData[targetIdx + 2] = color[2];
dstData[targetIdx + 3] = color[3];
}
}
}
}
}
/**
* 双线性插值采样
*/
_bilinearSample(data, width, height, x, y) {
if (x < 0 || x >= width - 1 || y < 0 || y >= height - 1) {
return null;
}
const x1 = Math.floor(x);
const y1 = Math.floor(y);
const x2 = x1 + 1;
const y2 = y1 + 1;
const fx = x - x1;
const fy = y - y1;
const getPixel = (px, py) => {
const idx = (py * width + px) * 4;
return [data[idx], data[idx + 1], data[idx + 2], data[idx + 3]];
};
const p1 = getPixel(x1, y1);
const p2 = getPixel(x2, y1);
const p3 = getPixel(x1, y2);
const p4 = getPixel(x2, y2);
return [
Math.round(
(1 - fx) * (1 - fy) * p1[0] +
fx * (1 - fy) * p2[0] +
(1 - fx) * fy * p3[0] +
fx * fy * p4[0]
),
Math.round(
(1 - fx) * (1 - fy) * p1[1] +
fx * (1 - fy) * p2[1] +
(1 - fx) * fy * p3[1] +
fx * fy * p4[1]
),
Math.round(
(1 - fx) * (1 - fy) * p1[2] +
fx * (1 - fy) * p2[2] +
(1 - fx) * fy * p3[2] +
fx * fy * p4[2]
),
Math.round(
(1 - fx) * (1 - fy) * p1[3] +
fx * (1 - fy) * p2[3] +
(1 - fx) * fy * p3[3] +
fx * fy * p4[3]
),
];
}
/**
* 网格液化算法 - 适用于推拉、捏合、展开模式
*/
_applyMeshDeformation(x, y, radius, strength) {
if (!this.mesh) return this.currentImageData;
// 使用现有的网格算法处理推拉、捏合、展开
const mode = this.currentMode;
const { distortion } = this.params;
this._applyDeformation(x, y, radius, strength, mode, distortion);
if (this.config.smoothingIterations > 0) {
this._smoothMesh();
}
return this._applyMeshToImage();
}
// ...existing mesh methods...
}

View File

@@ -38,7 +38,7 @@ export class LiquifyManager {
meshResolution: options.meshResolution || 64,
// 根据环境选择合适的渲染模式
forceCPU: true, // 默认不强制使用CPU
forceWebGL: false, // 优先使用WebGL模式
forceWebGL: true, // 优先使用WebGL模式
webglSizeThreshold: options.webglSizeThreshold || 500 * 500, // 降低阈值以更倾向使用WebGL
layerManager: options.layerManager || null,
canvas: options.canvas || null,
@@ -87,9 +87,19 @@ export class LiquifyManager {
* @param {Number} value 参数值
*/
setParam(param, value) {
console.log(`LiquifyManager 设置参数: ${param}=${value}`);
return this.enhancedManager.setParam(param, value);
}
/**
* 批量设置参数
* @param {Object} params 参数对象
*/
setParams(params) {
console.log("LiquifyManager 批量设置参数:", params);
return this.enhancedManager.setParams(params);
}
/**
* 获取当前参数
* @returns {Object} 当前参数对象
@@ -120,21 +130,14 @@ export class LiquifyManager {
return null;
}
// 确保设置正确的模式和参数
if (mode) {
this.enhancedManager.setMode(mode);
}
console.log(
`LiquifyManager.applyLiquify: 模式=${mode}, 坐标=(${x}, ${y}), 参数=`,
params
);
if (params) {
Object.entries(params).forEach(([key, value]) => {
this.enhancedManager.setParam(key, value);
});
}
// 应用液化变形
console.log(`应用液化变形, 模式=${mode}, 坐标=(${x}, ${y}), 参数=`, params);
try {
// 直接调用EnhancedLiquifyManager的applyLiquify方法
// 避免重复设置参数让EnhancedLiquifyManager处理参数设置
const resultData = await this.enhancedManager.applyLiquify(
targetObject,
mode,
@@ -146,6 +149,13 @@ export class LiquifyManager {
// 确保返回结果数据
if (!resultData) {
console.warn("液化变形没有返回结果数据");
} else {
console.log(
"✅ 液化变形成功,返回图像数据尺寸:",
resultData.width,
"x",
resultData.height
);
}
return resultData;
@@ -180,6 +190,22 @@ export class LiquifyManager {
return this.enhancedManager.getStatus();
}
/**
* 开始液化操作(记录初始点)
* @param {Number} x 初始X坐标
* @param {Number} y 初始Y坐标
*/
startLiquifyOperation(x, y) {
return this.enhancedManager.startLiquifyOperation(x, y);
}
/**
* 结束液化操作
*/
endLiquifyOperation() {
return this.enhancedManager.endLiquifyOperation();
}
/**
* 释放资源
*/

View File

@@ -0,0 +1,443 @@
/**
* 液化实时更新器
* 负责高效地更新液化效果到画布上避免频繁创建fabric对象导致的性能问题
*/
export class LiquifyRealTimeUpdater {
constructor(canvas, options = {}) {
this.canvas = canvas;
this.targetObject = null;
this.isUpdating = false;
this.updateQueue = [];
this.lastUpdateTime = 0;
this.currImage = options.currImage || { value: null };
// 配置选项
this.config = {
throttleTime: options.throttleTime || 16, // 60fps
maxQueueSize: options.maxQueueSize || 5,
useDirectUpdate: options.useDirectUpdate !== false, // 默认启用直接更新
imageQuality: options.imageQuality || 1.0, // 图像质量 (0.1-1.0)
skipRenderDuringDrag: options.skipRenderDuringDrag || false, // 拖拽时跳过渲染
};
// 临时canvas用于快速渲染
this.tempCanvas = document.createElement("canvas");
this.tempCtx = this.tempCanvas.getContext("2d");
// 高质量canvas用于最终输出
this.highQualityCanvas = document.createElement("canvas");
this.highQualityCtx = this.highQualityCanvas.getContext("2d");
// 当前缓存的图像数据
this.cachedDataURL = null;
this.pendingImageData = null;
this.renderingScheduled = false;
// 优化Canvas画布渲染设置
this.canvas.renderOnAddRemove = false; // 禁用自动渲染
this.canvas.skipOffscreen = true; // 跳过离屏元素渲染
}
/**
* 设置目标对象
* @param {Object} fabricObject fabric图像对象
*/
setTargetObject(fabricObject) {
this.targetObject = fabricObject;
if (fabricObject && fabricObject._element) {
// 设置临时canvas尺寸
this.tempCanvas.width = fabricObject.width;
this.tempCanvas.height = fabricObject.height;
// 设置高质量canvas尺寸
this.highQualityCanvas.width = fabricObject.width;
this.highQualityCanvas.height = fabricObject.height;
// 配置高质量渲染上下文
this.highQualityCtx.imageSmoothingEnabled = true;
this.highQualityCtx.imageSmoothingQuality = "high";
// 配置临时canvas上下文快速渲染
this.tempCtx.imageSmoothingEnabled = false; // 快速模式关闭平滑
}
}
/**
* 实时更新图像数据到画布
* @param {ImageData} imageData 新的图像数据
* @param {Boolean} isDrawing 是否正在绘制(拖拽过程中)
* @returns {Promise} 更新完成的Promise
*/
async updateImage(imageData, isDrawing = false) {
if (!this.targetObject || !imageData) {
return;
}
// 节流控制
const now = Date.now();
if (now - this.lastUpdateTime < this.config.throttleTime && isDrawing) {
// 在绘制过程中进行节流,缓存最新的图像数据
this.pendingImageData = imageData;
return;
}
this.lastUpdateTime = now;
if (isDrawing && this.config.useDirectUpdate) {
// 拖拽过程中使用快速更新(降低质量以提高性能)
this._fastUpdate(imageData);
} else {
// 拖拽结束后使用完整更新(最高质量)
await this._fullUpdate(imageData);
}
}
/**
* 智能图像质量更新
* 根据图像尺寸和设备性能动态调整质量
* @param {ImageData} imageData 图像数据
* @param {Boolean} isDrawing 是否正在绘制
* @private
*/
_getOptimalQuality(imageData, isDrawing) {
const pixelCount = imageData.width * imageData.height;
if (isDrawing) {
// 拖拽时根据图像大小调整质量
if (pixelCount > 1000000) {
// 大于1M像素
return 0.7;
} else if (pixelCount > 500000) {
// 大于500K像素
return 0.8;
} else {
return 0.9;
}
} else {
// 拖拽结束时始终使用最高质量
return 1.0;
}
}
/**
* 快速更新 - 直接修改现有对象的图像源
* @param {ImageData} imageData 图像数据
* @private
*/
_fastUpdate(imageData) {
if (!this.targetObject || !this.targetObject._element) {
return;
}
try {
// 将ImageData渲染到临时canvas快速模式
this.tempCtx.putImageData(imageData, 0, 0);
// 获取智能质量设置
const quality = this._getOptimalQuality(imageData, true);
// 直接更新fabric对象的图像源使用PNG格式保持质量
const targetElement = this.targetObject._element;
// 方案1: 直接设置src属性最高性能
const dataURL = this.tempCanvas.toDataURL("image/png", quality);
if (targetElement.src !== dataURL) {
targetElement.src = dataURL;
// 关键优化直接设置fabric对象为脏状态但不立即渲染
// this.targetObject.dirty = false; // 标记为不需要立即渲染
// this.canvas.renderOnAddRemove = true; // 恢复自动渲染
// this.renderingScheduled = false; // 重置渲染调度状态
this?.scheduleRender?.(); // 调度一次渲染
// 使用requestAnimationFrame进行批量渲染优化
// if (!this.renderingScheduled && !this.config.skipRenderDuringDrag) {
// this.renderingScheduled = true;
// requestIdleCallback(() => {
// this.canvas.renderAll();
// this.renderingScheduled = false;
// });
// }
} else {
console.warn(
"=================快速更新液化效果时,图像数据未变化,跳过更新"
);
}
} catch (error) {
console.error("快速更新液化效果失败:", error);
}
}
getImageData(imageData) {
// 使用高质量canvas进行最终渲染
this.highQualityCtx.putImageData(imageData, 0, 0);
// 生成高质量DataURLPNG格式最大质量
const dataURL = this.highQualityCanvas.toDataURL("image/png", 1.0);
return dataURL;
}
/**
* 完整更新 - 创建新的fabric对象
* @param {ImageData} imageData 图像数据
* @private
*/
async _fullUpdate(imageData) {
return new Promise((resolve, reject) => {
try {
// 使用高质量canvas进行最终渲染
this.highQualityCtx.putImageData(imageData, 0, 0);
// 生成高质量DataURLPNG格式最大质量
const dataURL = this.highQualityCanvas.toDataURL("image/png", 1.0);
// 如果DataURL没有变化跳过更新
if (this.cachedDataURL === dataURL) {
resolve();
return;
}
this.cachedDataURL = dataURL;
// 创建新的fabric图像对象保持最高质量
fabric.Image.fromURL(
dataURL,
(newImg) => {
try {
if (!this.targetObject) {
console.warn("目标对象为空,跳过更新");
resolve();
return;
}
// 保存原对象信息用于智能查找
const originalObjId = this.targetObject.id;
const originalObjLayerId = this.targetObject.layerId;
// 保留原对象的所有变换属性
const originalObj = this.targetObject;
newImg.set({
left: originalObj.left,
top: originalObj.top,
scaleX: originalObj.scaleX,
scaleY: originalObj.scaleY,
angle: originalObj.angle,
flipX: originalObj.flipX,
flipY: originalObj.flipY,
opacity: originalObj.opacity,
originX: originalObj.originX,
originY: originalObj.originY,
id: originalObj.id,
name: originalObj.name,
layerId: originalObj.layerId,
selected: false,
evented: originalObj.evented,
});
// 临时禁用画布自动渲染
const oldRenderOnAddRemove = this.canvas.renderOnAddRemove;
this.canvas.renderOnAddRemove = false;
// 智能查找和替换canvas上的对象
const allObjects = this.canvas.getObjects();
let targetIndex = allObjects.indexOf(originalObj);
// 如果直接查找失败尝试通过ID查找
if (targetIndex === -1 && originalObjId) {
targetIndex = allObjects.findIndex(
(obj) => obj.id === originalObjId
);
if (targetIndex !== -1) {
console.log(`通过ID找到目标对象: ${originalObjId}`);
// 更新目标对象引用
this.targetObject = allObjects[targetIndex];
}
}
// 如果通过ID查找仍然失败尝试通过图层ID查找
if (targetIndex === -1 && originalObjLayerId) {
targetIndex = allObjects.findIndex(
(obj) => obj.layerId === originalObjLayerId
);
if (targetIndex !== -1) {
console.log(`通过图层ID找到目标对象: ${originalObjLayerId}`);
// 更新目标对象引用
this.targetObject = allObjects[targetIndex];
}
}
if (targetIndex !== -1) {
// 找到目标对象,执行替换
this.canvas.remove(this.targetObject);
this.canvas.insertAt(newImg, targetIndex);
// 恢复自动渲染设置
this.canvas.renderOnAddRemove = oldRenderOnAddRemove;
// 更新目标对象引用
this.targetObject = newImg;
// 一次性重新渲染画布
this.canvas.renderAll();
console.log(`✅ 液化对象更新成功,位置: ${targetIndex}`);
resolve(newImg);
} else {
// 如果在画布中找不到对象,可能对象已被移除或引用已更新
console.warn(
"在画布中找不到目标对象,可能已被其他操作移除或替换"
);
// 恢复自动渲染设置
this.canvas.renderOnAddRemove = oldRenderOnAddRemove;
// 尝试添加新对象到画布末尾
this.canvas.add(newImg);
this.targetObject = newImg;
this.canvas.renderAll();
console.log("🔄 已将新对象添加到画布末尾");
resolve(newImg);
}
} catch (error) {
// 恢复自动渲染设置
this.canvas.renderOnAddRemove = oldRenderOnAddRemove;
console.error("更新fabric对象时出错:", error);
reject(error);
}
},
{ crossOrigin: "anonymous" }
); // 确保跨域支持
} catch (error) {
console.error("完整更新过程出错:", error);
reject(error);
}
});
}
/**
* 处理待处理的图像数据
* 在拖拽结束后调用,处理可能积压的更新
*/
async processPendingUpdates() {
if (this.pendingImageData && !this.isUpdating) {
this.isUpdating = true;
try {
await this._fullUpdate(this.pendingImageData);
this.pendingImageData = null;
} catch (error) {
console.error("处理待处理更新失败:", error);
} finally {
this.isUpdating = false;
}
}
}
/**
* 清理资源
*/
dispose() {
this.targetObject = null;
this.cachedDataURL = null;
this.pendingImageData = null;
this.updateQueue.length = 0;
// 清理临时canvas
if (this.tempCanvas) {
this.tempCanvas.width = 0;
this.tempCanvas.height = 0;
this.tempCanvas = null;
this.tempCtx = null;
}
}
/**
* 获取当前目标对象
* @returns {Object} 当前的fabric对象
*/
getTargetObject() {
return this.targetObject;
}
/**
* 强制进行完整更新
* @param {ImageData} imageData 图像数据
*/
async forceFullUpdate(imageData) {
return this._fullUpdate(imageData);
}
/**
* 启用拖拽模式 - 暂停渲染以提高性能
*/
enableDragMode() {
this.config.skipRenderDuringDrag = true;
this.canvas.renderOnAddRemove = false;
console.log("🚀 启用拖拽优化模式");
}
/**
* 禁用拖拽模式 - 恢复正常渲染
*/
disableDragMode() {
this.config.skipRenderDuringDrag = false;
this.canvas.renderOnAddRemove = true;
// 执行一次完整渲染
this.canvas.renderAll();
console.log("✅ 恢复正常渲染模式");
}
/**
* 获取当前目标对象
*/
getTargetObject() {
return this.targetObject;
}
/**
* 设置图像质量
* @param {Number} quality 质量值 (0.1-1.0)
*/
setImageQuality(quality) {
this.config.imageQuality = Math.max(0.1, Math.min(1.0, quality));
}
/**
* 优化的批量渲染方法
*/
scheduleRender() {
if (!this.renderingScheduled) {
this.renderingScheduled = true;
requestAnimationFrame(() => {
this.canvas.renderAll();
this.renderingScheduled = false;
});
}
}
/**
* 清理资源
*/
dispose() {
// 恢复canvas设置
this.canvas.renderOnAddRemove = true;
// 清理缓存
this.cachedDataURL = null;
this.pendingImageData = null;
// 清理canvas
if (this.tempCanvas) {
this.tempCanvas.width = 0;
this.tempCanvas.height = 0;
}
if (this.highQualityCanvas) {
this.highQualityCanvas.width = 0;
this.highQualityCanvas.height = 0;
}
console.log("🧹 液化实时更新器资源已清理");
}
}

View File

@@ -0,0 +1,431 @@
/**
* 液化面板状态管理器
* 负责管理液化操作的状态、性能优化和用户反馈
*/
export class LiquifyStateManager {
constructor(canvas, realtimeUpdater) {
this.canvas = canvas;
this.realtimeUpdater = realtimeUpdater;
// 状态管理
this.isOperating = false;
this.isDragging = false;
this.operationCount = 0;
this.startTime = null;
// 性能监控
this.performanceMetrics = {
totalOperations: 0,
totalTime: 0,
averageTime: 0,
maxTime: 0,
minTime: Infinity,
lastOperationTime: 0,
};
// 用户反馈
this.feedbackEnabled = true;
this.cursorCache = new Map();
// 设备性能检测
this.devicePerformance = this._detectDevicePerformance();
console.log(
"🎯 液化状态管理器已初始化,设备性能等级:",
this.devicePerformance
);
}
/**
* 开始液化操作
*/
startOperation() {
if (this.isOperating) return;
this.isOperating = true;
this.startTime = performance.now();
// 根据设备性能调整设置
this._adjustPerformanceSettings();
// 显示操作反馈
this._showOperationFeedback();
console.log("🚀 开始液化操作");
}
/**
* 开始拖拽
*/
startDrag() {
if (this.isDragging) return;
this.isDragging = true;
// 优化拖拽性能
this.realtimeUpdater?.enableDragMode();
// 更新鼠标样式
this._updateCursor("liquifying");
// 禁用不必要的画布功能
this._disableCanvasFeatures();
console.log("🖱️ 开始拖拽操作");
}
/**
* 结束拖拽
*/
async endDrag() {
if (!this.isDragging) return;
// 恢复鼠标样式
this._updateCursor("default");
// 恢复画布功能
this._enableCanvasFeatures();
// 处理待处理的更新
if (this.realtimeUpdater) {
try {
await this.realtimeUpdater.processPendingUpdates();
} catch (error) {
console.error("处理待处理更新失败:", error);
} finally {
this.isDragging = false;
// 恢复正常模式
this.realtimeUpdater?.disableDragMode();
// 结束液化操作 添加结果到命令中 更新当前激活图层对象
}
}
console.log("✅ 结束拖拽操作");
}
/**
* 结束液化操作
*/
endOperation() {
if (!this.isOperating) return;
const operationTime = performance.now() - this.startTime;
this._updatePerformanceMetrics(operationTime);
this.isOperating = false;
this.operationCount++;
// 隐藏操作反馈
this._hideOperationFeedback();
console.log(`⏱️ 液化操作完成,耗时: ${operationTime.toFixed(2)}ms`);
}
/**
* 记录单次变形操作
*/
recordDeformation(operationTime) {
this.performanceMetrics.totalOperations++;
this.performanceMetrics.totalTime += operationTime;
this.performanceMetrics.averageTime =
this.performanceMetrics.totalTime /
this.performanceMetrics.totalOperations;
this.performanceMetrics.maxTime = Math.max(
this.performanceMetrics.maxTime,
operationTime
);
this.performanceMetrics.minTime = Math.min(
this.performanceMetrics.minTime,
operationTime
);
this.performanceMetrics.lastOperationTime = operationTime;
}
/**
* 记录操作性能指标
* @param {Object} metrics 性能指标对象
*/
recordOperationMetrics(metrics) {
const {
operationTime,
operationType,
mode,
coordinates,
imageSize,
renderMode,
isRealTime,
} = metrics;
// 记录基础性能数据
this.recordDeformation(operationTime);
// 记录详细操作信息
this.performanceMetrics.lastOperation = {
type: operationType,
mode,
coordinates,
imageSize,
renderMode,
isRealTime,
timestamp: Date.now(),
};
// 根据性能数据动态调整设置
this._adaptivePerformanceOptimization(operationTime);
console.log(
`📊 记录性能指标: ${operationType}/${mode}, 耗时: ${operationTime.toFixed(
2
)}ms, 渲染模式: ${renderMode}`
);
}
/**
* 自适应性能优化
* @param {Number} operationTime 操作耗时
* @private
*/
_adaptivePerformanceOptimization(operationTime) {
if (!this.realtimeUpdater) return;
// 如果操作耗时过长,动态降低质量或增加节流时间
if (operationTime > 50 && this.devicePerformance !== "high") {
// 降低图像质量
const currentQuality = this.realtimeUpdater.config.imageQuality || 1.0;
if (currentQuality > 0.7) {
this.realtimeUpdater.setImageQuality(
Math.max(0.7, currentQuality - 0.1)
);
console.log("⚡ 自动降低图像质量以提升性能");
}
// 增加节流时间
if (this.realtimeUpdater.config.throttleTime < 33) {
this.realtimeUpdater.config.throttleTime = Math.min(
33,
this.realtimeUpdater.config.throttleTime + 8
);
console.log("⏱️ 自动增加节流时间以提升性能");
}
}
// 如果性能很好,可以适当提高质量
if (operationTime < 20 && this.devicePerformance === "high") {
const currentQuality = this.realtimeUpdater.config.imageQuality || 1.0;
if (currentQuality < 1.0) {
this.realtimeUpdater.setImageQuality(
Math.min(1.0, currentQuality + 0.05)
);
}
}
}
/**
* 获取性能报告
*/
getPerformanceReport() {
return {
...this.performanceMetrics,
devicePerformance: this.devicePerformance,
fps: this._calculateFPS(),
recommendations: this._generateRecommendations(),
};
}
/**
* 设置用户反馈
*/
setFeedbackEnabled(enabled) {
this.feedbackEnabled = enabled;
}
/**
* 清理资源
*/
dispose() {
this._enableCanvasFeatures();
this._updateCursor("default");
this.cursorCache.clear();
console.log("🧹 液化状态管理器已清理");
}
// === 私有方法 ===
/**
* 检测设备性能
*/
_detectDevicePerformance() {
// 检测硬件并发数
const cores = navigator.hardwareConcurrency || 4;
// 检测内存
const memory = navigator.deviceMemory || 4;
// 检测连接类型
const connection = navigator.connection;
const effectiveType = connection?.effectiveType || "4g";
// 简单的性能评分算法
let score = 0;
score += cores * 10;
score += memory * 5;
if (effectiveType === "4g") score += 10;
else if (effectiveType === "3g") score += 5;
// 性能等级
if (score >= 70) return "high";
if (score >= 40) return "medium";
return "low";
}
/**
* 根据设备性能调整设置
*/
_adjustPerformanceSettings() {
if (!this.realtimeUpdater) return;
switch (this.devicePerformance) {
case "high":
this.realtimeUpdater.setImageQuality(1.0);
this.realtimeUpdater.config.throttleTime = 8; // 120fps
break;
case "medium":
this.realtimeUpdater.setImageQuality(0.9);
this.realtimeUpdater.config.throttleTime = 16; // 60fps
break;
case "low":
this.realtimeUpdater.setImageQuality(0.8);
this.realtimeUpdater.config.throttleTime = 33; // 30fps
break;
}
}
/**
* 显示操作反馈
*/
_showOperationFeedback() {
if (!this.feedbackEnabled) return;
// 添加视觉反馈(可以是加载动画、进度条等)
document.body.style.cursor = "wait";
}
/**
* 隐藏操作反馈
*/
_hideOperationFeedback() {
if (!this.feedbackEnabled) return;
document.body.style.cursor = "default";
}
/**
* 更新鼠标样式
*/
_updateCursor(type) {
if (!this.feedbackEnabled) return;
const cursors = {
default: "default",
liquifying: "crosshair",
wait: "wait",
"not-allowed": "not-allowed",
};
const cursor = cursors[type] || "default";
if (this.canvas && this.canvas.upperCanvasEl) {
this.canvas.upperCanvasEl.style.cursor = cursor;
}
}
/**
* 禁用画布功能以提高性能
*/
_disableCanvasFeatures() {
if (!this.canvas) return;
// 保存原始设置
this._originalSettings = {
renderOnAddRemove: this.canvas.renderOnAddRemove,
skipOffscreen: this.canvas.skipOffscreen,
enableRetinaScaling: this.canvas.enableRetinaScaling,
};
// 应用性能优化设置
this.canvas.renderOnAddRemove = false;
this.canvas.skipOffscreen = true;
// 低性能设备关闭高分辨率支持
if (this.devicePerformance === "low") {
this.canvas.enableRetinaScaling = false;
}
}
/**
* 恢复画布功能
*/
_enableCanvasFeatures() {
if (!this.canvas || !this._originalSettings) return;
// 恢复原始设置
this.canvas.renderOnAddRemove = this._originalSettings.renderOnAddRemove;
this.canvas.skipOffscreen = this._originalSettings.skipOffscreen;
this.canvas.enableRetinaScaling =
this._originalSettings.enableRetinaScaling;
this._originalSettings = null;
}
/**
* 更新性能指标
*/
_updatePerformanceMetrics(operationTime) {
this.performanceMetrics.totalTime += operationTime;
this.performanceMetrics.averageTime =
this.performanceMetrics.totalTime / (this.operationCount + 1);
this.performanceMetrics.maxTime = Math.max(
this.performanceMetrics.maxTime,
operationTime
);
this.performanceMetrics.minTime = Math.min(
this.performanceMetrics.minTime,
operationTime
);
}
/**
* 计算FPS
*/
_calculateFPS() {
if (this.performanceMetrics.averageTime === 0) return 0;
return Math.round(1000 / this.performanceMetrics.averageTime);
}
/**
* 生成性能建议
*/
_generateRecommendations() {
const recommendations = [];
if (this.performanceMetrics.averageTime > 50) {
recommendations.push("操作响应较慢,建议降低图像尺寸或关闭高质量模式");
}
if (this.devicePerformance === "low") {
recommendations.push("检测到低性能设备,已自动启用性能优化模式");
}
const fps = this._calculateFPS();
if (fps < 30) {
recommendations.push("帧率较低,建议减少同时进行的操作或降低液化强度");
}
return recommendations;
}
}

View File

@@ -1,4 +1,4 @@
//import { fabric } from "fabric-with-all";
import { fabric } from "fabric-with-all";
/**
* 小地图管理器类

View File

@@ -1,4 +1,4 @@
//import { fabric } from "fabric-with-all";
import { fabric } from "fabric-with-all";
import { generateId } from "../../utils/helper";
import { OperationType } from "../../utils/layerHelper";
import {