Files
aida_front/src/component/Canvas/CanvasEditor/managers/ThumbnailManager.js

371 lines
9.5 KiB
JavaScript
Raw Normal View History

2025-06-09 10:25:54 +08:00
/**
* 缩略图管理器 - 负责生成和缓存图层和元素的预览缩略图
*/
export class ThumbnailManager {
constructor(canvas, options = {}) {
this.canvas = canvas;
this.layers = options.layers || []; // 图层管理器
this.layerThumbSize = options.layerThumbSize || { width: 48, height: 48 };
this.elementThumbSize = options.elementThumbSize || {
width: 36,
height: 36,
};
this.layerThumbnails = new Map(); // 图层缩略图缓存
this.elementThumbnails = new Map(); // 元素缩略图缓存
this.defaultThumbnail =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="; // 1x1 透明图
}
/**
* 生成图层缩略图
* @param {Object} layer 图层对象ID
*/
generateLayerThumbnail(layerId) {
// const layer = this?.layers.value?.find((layer) => layer.id === layerId);
// if (!layer) return;
// // 延迟执行避免阻塞UI
// requestAnimationFrame(() => {
// this._generateLayerThumbnailNow(layer);
// });
}
/**
* 立即生成图层缩略图
* @param {Object} layer 图层对象
* @private
*/
_generateLayerThumbnailNow(layer) {
if (
!layer ||
!this.canvas ||
!layer.fabricObjects ||
!layer.fabricObjects?.length
)
return;
try {
let thumbnail = null;
if (!layer.children?.length) {
// 如果是元素图层,直接生成元素缩略图
thumbnail = this._generateThumbnailFromObjects(
layer.fabricObjects,
this.layerThumbSize.width,
this.layerThumbSize.height
);
} else if (layer.type === "group" || layer.children?.length) {
const fabricObjects = layer.children.reduce((pre, next) => {
if (next.fabricObjects.length) {
pre.push(...next.fabricObjects);
}
return pre;
}, []);
// 如果是分组图层,合并所有子对象的缩略图
thumbnail = this._generateThumbnailFromObjects(
fabricObjects,
this.layerThumbSize.width,
this.layerThumbSize.height
);
}
// 保存到缩略图缓存
if (thumbnail) {
this.layerThumbnails.set(layer.id, thumbnail);
} else {
// 如果无法生成缩略图,使用默认缩略图
this.layerThumbnails.set(layer.id, this.defaultThumbnail);
}
} catch (error) {
console.error("生成图层缩略图出错:", error);
}
}
/**
* 生成元素缩略图
* @param {Object} element 元素对象
* @param {Object} fabricObject fabric对象
*/
generateElementThumbnail(element, fabricObject) {
if (!element || !element.id || !fabricObject) return;
// 延迟执行避免阻塞UI
requestAnimationFrame(() => {
try {
const thumbnail = this._generateThumbnailFromObject(
fabricObject,
this.elementThumbSize.width,
this.elementThumbSize.height
);
if (thumbnail) {
this.elementThumbnails.set(element.id, thumbnail);
}
} catch (error) {
console.error("生成元素缩略图出错:", error);
}
});
}
/**
* 从fabric对象生成缩略图
* @param {Object} obj fabric对象
* @param {Number} width 缩略图宽度
* @param {Number} height 缩略图高度
* @returns {String} 缩略图数据URL
* @private
*/
_generateThumbnailFromObject(obj, width, height) {
if (!obj || !this.canvas) return null;
// 保存对象状态
const originalState = {
active: obj.active,
visible: obj.visible,
left: obj.left,
top: obj.top,
scaleX: obj.scaleX,
scaleY: obj.scaleY,
opacity: obj.opacity,
};
// 临时修改对象状态
obj.set({
active: false,
visible: true,
opacity: 1,
});
// 创建临时画布
const tempCanvas = document.createElement("canvas");
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext("2d");
// 获取对象边界
const bounds = obj.getBoundingRect();
// 绘制缩略图
try {
// 清空画布
tempCtx.clearRect(0, 0, width, height);
// 计算缩放比例
const scaleFactorX = width / bounds.width;
const scaleFactorY = height / bounds.height;
const scaleFactor = Math.min(scaleFactorX, scaleFactorY) * 0.8; // 保留一些边距
// 居中绘制
const centerX = width / 2;
const centerY = height / 2;
tempCtx.save();
tempCtx.translate(centerX, centerY);
tempCtx.scale(scaleFactor, scaleFactor);
tempCtx.translate(
-bounds.left - bounds.width / 2,
-bounds.top - bounds.height / 2
);
// 绘制对象
obj.render(tempCtx);
tempCtx.restore();
// 转换为数据URL
const dataUrl = tempCanvas.toDataURL("image/png");
// 恢复对象状态
obj.set(originalState);
return dataUrl;
} catch (error) {
console.error("绘制对象缩略图出错:", error);
// 恢复对象状态
obj.set(originalState);
return null;
}
}
/**
* 从多个fabric对象生成组合缩略图
* @param {Array} objects fabric对象数组
* @param {Number} width 缩略图宽度
* @param {Number} height 缩略图高度
* @returns {String} 缩略图数据URL
* @private
*/
_generateThumbnailFromObjects(objects, width, height) {
if (!objects || !objects.length || !this.canvas) return null;
// 创建临时画布
const tempCanvas = document.createElement("canvas");
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext("2d");
// 计算所有对象的总边界
let minX = Infinity,
minY = Infinity,
maxX = -Infinity,
maxY = -Infinity;
objects.forEach((obj) => {
if (!obj.visible) return;
const bounds = obj.getBoundingRect();
minX = Math.min(minX, bounds.left);
minY = Math.min(minY, bounds.top);
maxX = Math.max(maxX, bounds.left + bounds.width);
maxY = Math.max(maxY, bounds.top + bounds.height);
});
const groupWidth = maxX - minX;
const groupHeight = maxY - minY;
// 如果没有有效对象返回null
if (groupWidth <= 0 || groupHeight <= 0) return null;
// 保存对象状态
const originalStates = objects.map((obj) => ({
obj,
state: {
active: obj.active,
visible: obj.visible,
opacity: obj.opacity,
},
}));
// 临时修改对象状态
originalStates.forEach((item) => {
item.obj.set({
active: false,
visible: true,
opacity: 1,
});
});
// 绘制缩略图
try {
// 清空画布
tempCtx.clearRect(0, 0, width, height);
// 计算缩放比例
const scaleFactorX = width / groupWidth;
const scaleFactorY = height / groupHeight;
const scaleFactor = Math.min(scaleFactorX, scaleFactorY) * 0.8; // 保留一些边距
// 居中绘制
const centerX = width / 2;
const centerY = height / 2;
tempCtx.save();
tempCtx.translate(centerX, centerY);
tempCtx.scale(scaleFactor, scaleFactor);
tempCtx.translate(-(minX + groupWidth / 2), -(minY + groupHeight / 2));
// 按顺序绘制所有对象
objects.forEach((obj) => {
if (obj.visible) {
obj.render(tempCtx);
}
});
tempCtx.restore();
// 转换为数据URL
const dataUrl = tempCanvas.toDataURL("image/png");
// 恢复对象状态
originalStates.forEach((item) => {
item.obj.set(item.state);
});
return dataUrl;
} catch (error) {
console.error("绘制组合缩略图出错:", error);
// 恢复对象状态
originalStates.forEach((item) => {
item.obj.set(item.state);
});
return null;
}
}
/**
* 批量生成图层缩略图
* @param {Array} layers 图层数组
*/
generateAllLayerThumbnails(layers) {
if (!layers || !Array.isArray(layers)) return;
// 使用requestAnimationFrame批量生成避免阻塞主线程
requestAnimationFrame(() => {
layers.forEach((layer) => {
if (layer && layer.id) {
this._generateLayerThumbnailNow(layer);
}
});
});
}
/**
* 获取图层缩略图
* @param {String} layerId 图层ID
* @returns {String|null} 缩略图URL或null
*/
getLayerThumbnail(layerId) {
if (!layerId) return null;
return this.layerThumbnails.get(layerId) || null;
}
/**
* 获取元素缩略图
* @param {String} elementId 元素ID
* @returns {String|null} 缩略图URL或null
*/
getElementThumbnail(elementId) {
if (!elementId) return null;
return this.elementThumbnails.get(elementId) || null;
}
/**
* 清除图层缩略图
* @param {String} layerId 图层ID
*/
clearLayerThumbnail(layerId) {
if (layerId && this.layerThumbnails.has(layerId)) {
this.layerThumbnails.delete(layerId);
}
}
/**
* 清除元素缩略图
* @param {String} elementId 元素ID
*/
clearElementThumbnail(elementId) {
if (elementId && this.elementThumbnails.has(elementId)) {
this.elementThumbnails.delete(elementId);
}
}
/**
* 清除所有缩略图
*/
clearAllThumbnails() {
this.layerThumbnails.clear();
this.elementThumbnails.clear();
}
/**
* 释放资源
*/
dispose() {
this.clearAllThumbnails();
this.canvas = null;
}
}