371 lines
9.5 KiB
JavaScript
371 lines
9.5 KiB
JavaScript
|
|
/**
|
|||
|
|
* 缩略图管理器 - 负责生成和缓存图层和元素的预览缩略图
|
|||
|
|
*/
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|