合并画布

This commit is contained in:
X1627315083
2025-06-22 13:52:28 +08:00
parent fd6d61a44a
commit 584f6a7db0
47 changed files with 4540 additions and 1952 deletions

View File

@@ -1,3 +1,7 @@
import { findObjectById } from "../utils/helper";
import { findLayerRecursively } from "../utils/layerHelper";
import { createRasterizedImage } from "../utils/rasterizedImage";
/**
* 缩略图管理器 - 负责生成和缓存图层和元素的预览缩略图
*/
@@ -6,12 +10,8 @@ export class ThumbnailManager {
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 透明图
}
@@ -20,279 +20,31 @@ export class ThumbnailManager {
* 生成图层缩略图
* @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;
async generateLayerThumbnail(layerId) {
const fabricObjects = this._collectLayersAndObjects(layerId);
// 延迟执行避免阻塞UI
requestAnimationFrame(() => {
try {
const thumbnail = this._generateThumbnailFromObject(
fabricObject,
this.elementThumbSize.width,
this.elementThumbSize.height
);
fabricObjects.length > 0 &&
requestAnimationFrame(async () => {
const base64 = await this._generateLayerThumbnailNow(fabricObjects);
this.layerThumbnails.set(layerId, base64);
try {
const { layer, parent } = findLayerRecursively(
this.layers.value,
layerId
);
if (layer) {
layer.thumbnailUrl = base64; // 更新图层对象的缩略图
}
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);
if (parent) {
// 如果是组图层,则同步更新父图层的缩略图
this.generateLayerThumbnail(parent.id);
}
} catch (error) {
console.error("生成图层缩略图时出错:", error);
}
});
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;
}
}
/**
@@ -303,13 +55,133 @@ export class ThumbnailManager {
if (!layers || !Array.isArray(layers)) return;
// 使用requestAnimationFrame批量生成避免阻塞主线程
requestAnimationFrame(() => {
layers.forEach((layer) => {
if (layer && layer.id) {
this._generateLayerThumbnailNow(layer);
layers.forEach((layer) => {
if (layer && layer.id) {
this.generateLayerThumbnail(layer.id);
if (layer.children && layer.children.length) {
this.generateLayerThumbnail(layer.id);
}
}
});
}
// 生成图片
async _generateLayerThumbnailNow(fabricObjects) {
if (!fabricObjects || fabricObjects.length === 0) {
console.warn("⚠️ 没有对象需要生成缩略图,返回默认缩略图");
return this.defaultThumbnail;
}
return await createRasterizedImage({
canvas: this.canvas, // 画布对象 必填
fabricObjects, // 要栅格化的对象列表 - 按顺序 必填
// maskObject = null, // 用于裁剪的对象 - 可选 // TODO: 后期看是否需要裁剪
trimWhitespace: true, // 是否裁剪空白区域
trimPadding: 2, // 裁剪边距
quality: 0.8, // 图像质量
format: "png", // 图像格式
scaleFactor: 1, // 高清倍数 - 默认是画布的高清倍数
isReturenDataURL: true, // 是否返回DataURL而不是fabric.Image对象
});
}
/**
* 收集要栅格化的图层和对象
* @private
*/
_collectLayersAndObjects(layerId) {
if (!layerId) {
console.warn("⚠️ 无效的图层ID无法收集对象");
return [];
}
const { layer } = findLayerRecursively(this.layers.value, layerId);
let layersToRasterize = [];
if (layer.children && layer.children.length > 0) {
// 组图层:收集自身和所有子图层
layersToRasterize = this._collectLayersToRasterize(layer);
} else {
// 普通图层:只收集自身
layersToRasterize = [layer];
}
// 收集所有图层的fabricObjects并按画布z-index顺序排序
const allCanvasObjects = this.canvas.getObjects();
const objectsWithZIndex = [];
layersToRasterize.forEach((layer) => {
if (layer.fabricObject) {
// 如果图层本身有fabricObject直接添加
const { object } = findObjectById(this.canvas, layer.fabricObject.id);
if (object) {
const zIndex = allCanvasObjects.indexOf(object);
objectsWithZIndex.push({
object: object,
zIndex: zIndex,
layerObj: layer.fabricObject,
});
}
}
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
layer.fabricObjects.forEach((layerObj) => {
if (layerObj && layerObj.id) {
const { object } = findObjectById(this.canvas, layerObj.id);
if (object) {
// 获取对象在画布中的z-index数组索引
const zIndex = allCanvasObjects.indexOf(object);
objectsWithZIndex.push({
object: object,
zIndex: zIndex,
layerObj: layerObj,
});
}
}
});
}
});
// 按z-index排序确保保持原有的渲染顺序
objectsWithZIndex.sort((a, b) => a.zIndex - b.zIndex);
// 提取排序后的对象
const objectsToRasterize = objectsWithZIndex.map((item) => item.object);
console.log(
`📊 收集到 ${layersToRasterize.length} 个图层,${objectsToRasterize.length} 个对象进行栅格化`
);
console.log(
"🔢 对象z-index顺序:",
objectsWithZIndex.map((item) => ({
id: item.object.id,
type: item.object.type,
zIndex: item.zIndex,
}))
);
return objectsToRasterize;
}
/**
* 收集要栅格化的图层(递归收集子图层)
* @param {Object} sourceLayer 源图层
* @returns {Array} 图层数组
* @private
*/
_collectLayersToRasterize(sourceLayer) {
const result = [sourceLayer];
// 如果是组图层,收集所有子图层
if (sourceLayer.children && sourceLayer.children.length > 0) {
sourceLayer.children.forEach((childLayer) => {
if (childLayer) {
result.push(...this._collectLayersToRasterize(childLayer));
}
});
});
}
return result;
}
/**
@@ -322,16 +194,6 @@ export class ThumbnailManager {
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
@@ -342,22 +204,11 @@ export class ThumbnailManager {
}
}
/**
* 清除元素缩略图
* @param {String} elementId 元素ID
*/
clearElementThumbnail(elementId) {
if (elementId && this.elementThumbnails.has(elementId)) {
this.elementThumbnails.delete(elementId);
}
}
/**
* 清除所有缩略图
*/
clearAllThumbnails() {
this.layerThumbnails.clear();
this.elementThumbnails.clear();
}
/**