feat: 1.固定图层缩略图(完成)
2.工具栏新增插槽(完成) 3.loadJSON的元素顺序回发生错误
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
// 栅格化帮助
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { createStaticCanvas } from "./canvasFactory";
|
||||
/**
|
||||
* 创建栅格化图像
|
||||
* 使用增强版栅格化方法,不受原始画布变换影响
|
||||
* 使用组对象方式,避免边界计算误差
|
||||
* @returns {Promise<fabric.Image>} 栅格化后的图像对象
|
||||
* @private
|
||||
*/
|
||||
@@ -11,7 +12,7 @@ export const createRasterizedImage = async ({
|
||||
fabricObjects = [], // 要栅格化的对象列表 - 按顺序 必填
|
||||
maskObject = null, // 用于裁剪的对象 - 可选
|
||||
trimWhitespace = true, // 是否裁剪空白区域
|
||||
trimPadding = 1, // 裁剪边距
|
||||
trimPadding = 0, // 裁剪边距
|
||||
quality = 1.0, // 图像质量
|
||||
format = "png", // 图像格式
|
||||
scaleFactor = 2, // 高清倍数 - 默认是画布的高清倍数
|
||||
@@ -33,22 +34,12 @@ export const createRasterizedImage = async ({
|
||||
currentZoom
|
||||
);
|
||||
|
||||
scaleFactor = Math.min(scaleFactor, 3); // 最大不能大于3
|
||||
|
||||
console.log(`高清倍数: ${scaleFactor}, 当前缩放: ${currentZoom}`);
|
||||
|
||||
// 计算绝对边界框(原始尺寸)和相对边界框(当前缩放后的尺寸)
|
||||
const { absoluteBounds, relativeBounds } = calculateBounds(fabricObjects);
|
||||
|
||||
console.log("📏 绝对边界框:", absoluteBounds);
|
||||
console.log("📏 相对边界框:", relativeBounds);
|
||||
|
||||
// 使用绝对边界框创建高质量的离屏渲染
|
||||
const rasterizedImage = await createOffscreenRasterization({
|
||||
// 使用组对象方式创建栅格化图像
|
||||
const rasterizedImage = await createRasterizedImageWithGroup({
|
||||
canvas,
|
||||
objects: fabricObjects,
|
||||
absoluteBounds,
|
||||
relativeBounds,
|
||||
scaleFactor,
|
||||
maskObject,
|
||||
trimWhitespace,
|
||||
@@ -80,8 +71,6 @@ export const createRasterizedImage = async ({
|
||||
type: "rasterized",
|
||||
rasterizedAt: new Date().toISOString(),
|
||||
objectCount: fabricObjects.length,
|
||||
absoluteBounds,
|
||||
relativeBounds,
|
||||
originalZoom: currentZoom,
|
||||
},
|
||||
});
|
||||
@@ -97,84 +86,13 @@ export const createRasterizedImage = async ({
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算对象的绝对边界框和相对边界框
|
||||
* @param {Array} fabricObjects fabric对象数组
|
||||
* @returns {Object} 包含绝对边界框和相对边界框的对象
|
||||
*/
|
||||
const calculateBounds = (fabricObjects) => {
|
||||
if (fabricObjects.length === 0) {
|
||||
console.warn("⚠️ 没有对象,无法计算边界框");
|
||||
return { absoluteBounds: null, relativeBounds: null };
|
||||
}
|
||||
|
||||
let absoluteBounds = null;
|
||||
let relativeBounds = null;
|
||||
|
||||
fabricObjects.forEach((obj, index) => {
|
||||
// 获取相对边界框(考虑画布缩放和平移)
|
||||
const relativeBound = obj.getBoundingRect();
|
||||
// 获取绝对边界框(原始大小和位置)
|
||||
const absoluteBound = obj.getBoundingRect(true, true);
|
||||
|
||||
console.log(`对象 ${obj.id || index} 边界框比较:`, {
|
||||
relative: relativeBound,
|
||||
absolute: absoluteBound,
|
||||
scaleX: obj.scaleX,
|
||||
scaleY: obj.scaleY,
|
||||
});
|
||||
|
||||
// 计算绝对边界框的累积范围
|
||||
if (!absoluteBounds) {
|
||||
absoluteBounds = { ...absoluteBound };
|
||||
} else {
|
||||
const right = Math.max(
|
||||
absoluteBounds.left + absoluteBounds.width,
|
||||
absoluteBound.left + absoluteBound.width
|
||||
);
|
||||
const bottom = Math.max(
|
||||
absoluteBounds.top + absoluteBounds.height,
|
||||
absoluteBound.top + absoluteBound.height
|
||||
);
|
||||
|
||||
absoluteBounds.left = Math.min(absoluteBounds.left, absoluteBound.left);
|
||||
absoluteBounds.top = Math.min(absoluteBounds.top, absoluteBound.top);
|
||||
absoluteBounds.width = right - absoluteBounds.left;
|
||||
absoluteBounds.height = bottom - absoluteBounds.top;
|
||||
}
|
||||
|
||||
// 计算相对边界框的累积范围
|
||||
if (!relativeBounds) {
|
||||
relativeBounds = { ...relativeBound };
|
||||
} else {
|
||||
const right = Math.max(
|
||||
relativeBounds.left + relativeBounds.width,
|
||||
relativeBound.left + relativeBound.width
|
||||
);
|
||||
const bottom = Math.max(
|
||||
relativeBounds.top + relativeBounds.height,
|
||||
relativeBound.top + relativeBound.height
|
||||
);
|
||||
|
||||
relativeBounds.left = Math.min(relativeBounds.left, relativeBound.left);
|
||||
relativeBounds.top = Math.min(relativeBounds.top, relativeBound.top);
|
||||
relativeBounds.width = right - relativeBounds.left;
|
||||
relativeBounds.height = bottom - relativeBounds.top;
|
||||
}
|
||||
});
|
||||
|
||||
return { absoluteBounds, relativeBounds };
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建离屏栅格化渲染
|
||||
* 使用组对象方式创建栅格化图像
|
||||
* @param {Object} options 渲染选项
|
||||
* @returns {Promise<fabric.Image>} 栅格化后的图像对象
|
||||
*/
|
||||
const createOffscreenRasterization = async ({
|
||||
const createRasterizedImageWithGroup = async ({
|
||||
canvas,
|
||||
objects,
|
||||
absoluteBounds,
|
||||
relativeBounds,
|
||||
scaleFactor,
|
||||
maskObject,
|
||||
trimWhitespace,
|
||||
@@ -185,49 +103,71 @@ const createOffscreenRasterization = async ({
|
||||
isReturenDataURL,
|
||||
}) => {
|
||||
try {
|
||||
// 创建离屏画布,使用绝对尺寸以保证高质量
|
||||
const offscreenCanvas = new fabric.Canvas();
|
||||
// 创建离屏画布
|
||||
const offscreenCanvas = createStaticCanvas();
|
||||
|
||||
// 设置离屏画布尺寸为绝对边界框大小,并应用高清倍数
|
||||
const canvasWidth = Math.ceil(absoluteBounds.width * scaleFactor);
|
||||
const canvasHeight = Math.ceil(absoluteBounds.height * scaleFactor);
|
||||
// 克隆所有对象
|
||||
const clonedObjects = [];
|
||||
for (const obj of objects) {
|
||||
const clonedObj = await cloneObjectAsync(obj);
|
||||
clonedObj.set({
|
||||
select: false,
|
||||
evented: false,
|
||||
hasControls: false,
|
||||
});
|
||||
clonedObjects.push(clonedObj);
|
||||
}
|
||||
|
||||
// 创建组对象
|
||||
const group = new fabric.Group(clonedObjects, {
|
||||
originX: "left",
|
||||
originY: "top",
|
||||
});
|
||||
|
||||
// 获取组的绝对边界框
|
||||
const groupBounds = group.getBoundingRect(true, true);
|
||||
console.log("📏 组边界框:", groupBounds);
|
||||
|
||||
// 设置离屏画布尺寸,使用组的边界大小
|
||||
const canvasWidth = Math.ceil(groupBounds.width * scaleFactor);
|
||||
const canvasHeight = Math.ceil(groupBounds.height * scaleFactor);
|
||||
|
||||
offscreenCanvas.setDimensions({
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
hasControls: false,
|
||||
});
|
||||
|
||||
// 设置离屏画布的缩放,确保对象以原始尺寸渲染
|
||||
// offscreenCanvas.setZoom(scaleFactor);
|
||||
|
||||
console.log(
|
||||
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
|
||||
);
|
||||
|
||||
// 克隆对象到离屏画布
|
||||
const clonedObjects = [];
|
||||
for (const obj of objects) {
|
||||
const clonedObj = await cloneObjectAsync(obj);
|
||||
// 调整组的位置,让它位于画布的左上角
|
||||
group.set({
|
||||
left: 0,
|
||||
top: 0,
|
||||
});
|
||||
|
||||
// 调整对象位置,相对于绝对边界框的左上角
|
||||
// const absoluteObjBounds = obj.getBoundingRect(true, true);
|
||||
clonedObj.set({
|
||||
left: clonedObj.left - absoluteBounds.left,
|
||||
top: clonedObj.top - absoluteBounds.top,
|
||||
});
|
||||
// 取消对象激活
|
||||
group.set({
|
||||
selectable: false, // 禁用组的选择
|
||||
evented: false, // 禁用组的事件
|
||||
});
|
||||
|
||||
clonedObjects.push(clonedObj);
|
||||
offscreenCanvas.add(clonedObj);
|
||||
// 将组添加到离屏画布
|
||||
offscreenCanvas.add(group);
|
||||
|
||||
// 设置离屏画布的缩放
|
||||
offscreenCanvas.setZoom(scaleFactor);
|
||||
|
||||
// 如果有遮罩对象,应用遮罩
|
||||
if (maskObject) {
|
||||
await applyMaskToCanvas(offscreenCanvas, maskObject, groupBounds);
|
||||
}
|
||||
|
||||
// 渲染离屏画布
|
||||
offscreenCanvas.renderAll();
|
||||
|
||||
// 如果有遮罩对象,应用遮罩
|
||||
if (maskObject) {
|
||||
await applyMaskToCanvas(offscreenCanvas, maskObject, absoluteBounds);
|
||||
}
|
||||
|
||||
// 生成图像数据
|
||||
const dataURL = offscreenCanvas.toDataURL({
|
||||
format,
|
||||
@@ -236,22 +176,30 @@ const createOffscreenRasterization = async ({
|
||||
});
|
||||
|
||||
if (isReturenDataURL) {
|
||||
// 清理离屏画布
|
||||
offscreenCanvas.dispose();
|
||||
return dataURL; // 如果需要返回DataURL
|
||||
}
|
||||
|
||||
// 清理离屏画布
|
||||
offscreenCanvas.dispose();
|
||||
|
||||
// 创建fabric.Image对象
|
||||
const fabricImage = await createFabricImageFromDataURL(dataURL);
|
||||
|
||||
// // 应用变换到fabric图像
|
||||
// 设置图像的位置和缩放,使其与原始组的位置和大小匹配
|
||||
fabricImage.set({
|
||||
...absoluteBounds,
|
||||
scaleX: 1 / scaleFactor, // 由于我们生成的图像是高清版本,需要缩放回原始大小
|
||||
scaleY: 1 / scaleFactor,
|
||||
left: groupBounds.left + groupBounds.width / 2, // 设置为组中心点
|
||||
top: groupBounds.top + groupBounds.height / 2, // 设置为组中心点
|
||||
originX: "center",
|
||||
originY: "center",
|
||||
});
|
||||
|
||||
return fabricImage;
|
||||
} catch (error) {
|
||||
console.error("离屏栅格化失败:", error);
|
||||
console.error("组对象栅格化失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -290,37 +238,6 @@ const createFabricImageFromDataURL = (dataURL) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算栅格化图像在当前画布上的正确变换
|
||||
* @param {Object} params 计算参数
|
||||
* @returns {Object} 变换属性对象
|
||||
*/
|
||||
const calculateImageTransform = ({
|
||||
absoluteBounds,
|
||||
relativeBounds,
|
||||
currentZoom,
|
||||
scaleFactor,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
}) => {
|
||||
// 计算缩放比例:相对尺寸 / 绝对尺寸
|
||||
const scaleX = relativeBounds.width / absoluteBounds.width;
|
||||
const scaleY = relativeBounds.height / absoluteBounds.height;
|
||||
|
||||
// 由于我们生成的图像是基于绝对尺寸的高清版本,需要考虑scaleFactor
|
||||
const finalScaleX = scaleX / scaleFactor;
|
||||
const finalScaleY = scaleY / scaleFactor;
|
||||
|
||||
return {
|
||||
left: relativeBounds.left + relativeBounds.width / 2, // 设置为中心点
|
||||
top: relativeBounds.top + relativeBounds.height / 2, // 设置为中心点
|
||||
// scaleX: finalScaleX,
|
||||
// scaleY: finalScaleY,
|
||||
originX: "center",
|
||||
originY: "center",
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 应用遮罩到画布(如果需要)
|
||||
* @param {fabric.Canvas} canvas 目标画布
|
||||
@@ -333,7 +250,26 @@ const applyMaskToCanvas = async (canvas, maskObject, bounds) => {
|
||||
console.log("应用遮罩功能待实现");
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取对象组的边界框
|
||||
* @param {Array} fabricObjects fabric对象数组
|
||||
* @returns {Object} 边界框信息
|
||||
*/
|
||||
export const getObjectsBounds = (fabricObjects) => {
|
||||
const { absoluteBounds } = calculateBounds(fabricObjects);
|
||||
return absoluteBounds;
|
||||
if (fabricObjects.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建临时组来获取准确的边界框
|
||||
const tempGroup = new fabric.Group([...fabricObjects], {
|
||||
originX: "left",
|
||||
originY: "top",
|
||||
});
|
||||
|
||||
const bounds = tempGroup.getBoundingRect(true, true);
|
||||
|
||||
// 清理临时组
|
||||
tempGroup.destroy();
|
||||
|
||||
return bounds;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user