合并画布代码
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user