424 lines
11 KiB
JavaScript
424 lines
11 KiB
JavaScript
/**
|
|
* 液化对象引用管理器
|
|
* 专门处理液化操作中的对象引用管理,避免引用丢失问题
|
|
*/
|
|
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;
|
|
}
|