734 lines
20 KiB
JavaScript
734 lines
20 KiB
JavaScript
// 栅格化帮助
|
||
import { fabric } from "fabric-with-all";
|
||
|
||
/**
|
||
* 创建栅格化图像 - 重构版本
|
||
* 采用复制原对象+裁剪路径的方式,保持原始质量和准确位置
|
||
* @returns {Promise<fabric.Image|fabric.Group|string>} 栅格化后的图像对象或DataURL
|
||
* @private
|
||
*/
|
||
export const createRasterizedImage = async ({
|
||
canvas, // 画布对象 必填
|
||
fabricObjects = [], // 要栅格化的对象列表 - 按顺序 必填
|
||
maskObject = null, // 用于裁剪的对象 - 可选
|
||
clipPath = null, // 裁剪路径对象 - 可选,优先级高于maskObject
|
||
trimWhitespace = true, // 是否裁剪空白区域
|
||
trimPadding = 0, // 裁剪边距
|
||
quality = 1.0, // 图像质量
|
||
format = "png", // 图像格式
|
||
scaleFactor = 1, // 高清倍数 - 默认是画布的高清倍数
|
||
isReturenDataURL = false, // 是否返回DataURL而不是fabric.Image对象
|
||
preserveOriginalQuality = true, // 是否保持原始质量(新增)
|
||
} = {}) => {
|
||
try {
|
||
console.log(`📊 开始栅格化 ${fabricObjects.length} 个对象`);
|
||
|
||
// 确保有对象需要栅格化
|
||
if (fabricObjects.length === 0) {
|
||
console.warn("⚠️ 没有对象需要栅格化,返回空图像");
|
||
return null;
|
||
}
|
||
|
||
// 处理裁剪对象,优先使用clipPath
|
||
const clippingObject = clipPath || maskObject;
|
||
// 如果保持原始质量且有裁剪对象,使用新的裁剪方法
|
||
if (preserveOriginalQuality && clippingObject) {
|
||
return await createClippedObjects({
|
||
canvas,
|
||
fabricObjects,
|
||
clippingObject,
|
||
isReturenDataURL,
|
||
});
|
||
}
|
||
|
||
// 如果只是简单复制而不需要裁剪,直接克隆对象
|
||
if (!clippingObject) {
|
||
return await createSimpleClone({
|
||
canvas,
|
||
fabricObjects,
|
||
isReturenDataURL,
|
||
quality,
|
||
format,
|
||
});
|
||
}
|
||
|
||
// 兼容原有的离屏渲染方法(作为备选方案)
|
||
return await createLegacyRasterization({
|
||
canvas,
|
||
fabricObjects,
|
||
clippingObject,
|
||
scaleFactor,
|
||
quality,
|
||
format,
|
||
isReturenDataURL,
|
||
});
|
||
} catch (error) {
|
||
console.error("创建栅格化图像失败:", error);
|
||
throw new Error(`栅格化失败: ${error.message}`);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 创建带裁剪的对象 - 新方法
|
||
* 直接复制原对象并应用裁剪路径,保持原始质量
|
||
*/
|
||
const createClippedObjects = async ({
|
||
canvas,
|
||
fabricObjects,
|
||
clippingObject,
|
||
isReturenDataURL,
|
||
}) => {
|
||
try {
|
||
console.log("🎯 使用新的裁剪方法创建对象");
|
||
|
||
// 获取选区边界框
|
||
const selectionBounds = clippingObject.getBoundingRect(true);
|
||
console.log("📐 选区边界框:", selectionBounds);
|
||
// 方法1:如果只需要返回DataURL,使用画布裁剪方法
|
||
if (isReturenDataURL) {
|
||
return await createClippedDataURLByCanvas({
|
||
canvas,
|
||
fabricObjects,
|
||
clippingObject,
|
||
selectionBounds,
|
||
});
|
||
}
|
||
|
||
// 方法2:如果需要返回fabric对象,先生成DataURL再转换为fabric对象
|
||
const clippedDataURL = await createClippedDataURLByCanvas({
|
||
canvas,
|
||
fabricObjects,
|
||
clippingObject,
|
||
selectionBounds,
|
||
});
|
||
|
||
// 将DataURL转换为fabric.Image对象
|
||
const fabricImage = await createFabricImageFromDataURL(clippedDataURL);
|
||
|
||
// 使用fabric原生方法恢复到选区的原始大小和位置
|
||
fabricImage.scaleToWidth(selectionBounds.width);
|
||
fabricImage.scaleToHeight(selectionBounds.height);
|
||
|
||
// 设置到选区的原始位置(中心点)
|
||
fabricImage.set({
|
||
left: selectionBounds.left + selectionBounds.width / 2,
|
||
top: selectionBounds.top + selectionBounds.height / 2,
|
||
originX: "center",
|
||
originY: "center",
|
||
selectable: true,
|
||
evented: true,
|
||
hasControls: true,
|
||
hasBorders: true,
|
||
custom: {
|
||
type: "clipped",
|
||
clippedAt: new Date().toISOString(),
|
||
hasClipping: true,
|
||
preservedQuality: true,
|
||
originalBounds: selectionBounds,
|
||
restoredToOriginalSize: true,
|
||
},
|
||
});
|
||
|
||
// 更新坐标
|
||
fabricImage.setCoords();
|
||
|
||
console.log("✅ 返回裁剪后的fabric对象,已恢复到原始大小和位置");
|
||
return fabricImage;
|
||
} catch (error) {
|
||
console.error("创建裁剪对象失败:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 通过画布裁剪生成DataURL
|
||
* 裁剪掉选区以外的内容,保持和选区大小一致
|
||
*/
|
||
const createClippedDataURLByCanvas = async ({
|
||
canvas,
|
||
fabricObjects,
|
||
clippingObject,
|
||
selectionBounds,
|
||
}) => {
|
||
try {
|
||
console.log("🖼️ 使用画布裁剪方法生成DataURL");
|
||
|
||
// 创建临时画布,尺寸与选区完全一致
|
||
const tempCanvas = new fabric.StaticCanvas();
|
||
|
||
// 使用高分辨率以保证质量
|
||
const pixelRatio = window.devicePixelRatio || 1;
|
||
const qualityMultiplier = Math.max(2, pixelRatio);
|
||
|
||
const canvasWidth = Math.ceil(selectionBounds.width * qualityMultiplier);
|
||
const canvasHeight = Math.ceil(selectionBounds.height * qualityMultiplier);
|
||
|
||
tempCanvas.setDimensions({
|
||
width: canvasWidth,
|
||
height: canvasHeight,
|
||
});
|
||
|
||
console.log(
|
||
`📏 临时画布尺寸: ${canvasWidth}x${canvasHeight} (质量倍数: ${qualityMultiplier})`
|
||
);
|
||
|
||
// 克隆并添加所有需要裁剪的对象
|
||
for (const obj of fabricObjects) {
|
||
const clonedObj = await cloneObjectAsync(obj);
|
||
|
||
// 调整对象位置:将选区左上角作为新的原点(0,0)
|
||
// 同时应用质量倍数缩放
|
||
clonedObj.set({
|
||
left: (clonedObj.left - selectionBounds.left) * qualityMultiplier,
|
||
top: (clonedObj.top - selectionBounds.top) * qualityMultiplier,
|
||
scaleX: (clonedObj.scaleX || 1) * qualityMultiplier,
|
||
scaleY: (clonedObj.scaleY || 1) * qualityMultiplier,
|
||
});
|
||
|
||
tempCanvas.add(clonedObj);
|
||
}
|
||
|
||
// 克隆裁剪路径并调整位置
|
||
const clipPath = await cloneObjectAsync(clippingObject);
|
||
clipPath.set({
|
||
left: (clipPath.left - selectionBounds.left) * qualityMultiplier,
|
||
top: (clipPath.top - selectionBounds.top) * qualityMultiplier,
|
||
scaleX: (clipPath.scaleX || 1) * qualityMultiplier,
|
||
scaleY: (clipPath.scaleY || 1) * qualityMultiplier,
|
||
fill: "transparent",
|
||
stroke: "",
|
||
strokeWidth: 0,
|
||
absolutePositioned: true,
|
||
});
|
||
|
||
// 为整个画布设置裁剪路径
|
||
tempCanvas.clipPath = clipPath;
|
||
|
||
// 渲染画布
|
||
tempCanvas.renderAll();
|
||
|
||
// 生成高质量DataURL
|
||
const dataURL = tempCanvas.toDataURL({
|
||
format: "png",
|
||
quality: 1.0,
|
||
multiplier: 1, // 已经通过尺寸处理了缩放
|
||
});
|
||
|
||
// 清理临时画布
|
||
tempCanvas.dispose();
|
||
|
||
console.log("✅ 画布裁剪完成,生成DataURL");
|
||
return dataURL;
|
||
} catch (error) {
|
||
console.error("画布裁剪失败:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 创建简单克隆对象
|
||
* 当不需要裁剪时,直接克隆原对象
|
||
*/
|
||
const createSimpleClone = async ({
|
||
canvas,
|
||
fabricObjects,
|
||
isReturenDataURL,
|
||
quality,
|
||
format,
|
||
}) => {
|
||
try {
|
||
console.log("📋 创建简单克隆对象");
|
||
|
||
const clonedObjects = [];
|
||
|
||
// 克隆所有对象
|
||
for (const obj of fabricObjects) {
|
||
const clonedObj = await cloneObjectAsync(obj);
|
||
|
||
clonedObj.set({
|
||
selectable: true,
|
||
evented: true,
|
||
hasControls: true,
|
||
hasBorders: true,
|
||
custom: {
|
||
...clonedObj.custom,
|
||
type: "cloned",
|
||
clonedAt: new Date().toISOString(),
|
||
preservedQuality: true,
|
||
},
|
||
});
|
||
|
||
clonedObjects.push(clonedObj);
|
||
}
|
||
|
||
// 如果需要返回DataURL,需要渲染
|
||
if (isReturenDataURL) {
|
||
return await renderObjectsToDataURL(clonedObjects, quality, format);
|
||
}
|
||
|
||
// 如果只有一个对象,直接返回
|
||
if (clonedObjects.length === 1) {
|
||
return clonedObjects[0];
|
||
}
|
||
|
||
// 创建组合
|
||
const group = new fabric.Group(clonedObjects, {
|
||
selectable: true,
|
||
evented: true,
|
||
hasControls: true,
|
||
hasBorders: true,
|
||
custom: {
|
||
type: "clonedGroup",
|
||
clonedAt: new Date().toISOString(),
|
||
objectCount: clonedObjects.length,
|
||
preservedQuality: true,
|
||
},
|
||
});
|
||
|
||
return group;
|
||
} catch (error) {
|
||
console.error("创建简单克隆失败:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 将对象渲染为DataURL
|
||
*/
|
||
const renderObjectsToDataURL = async (objects, quality, format) => {
|
||
try {
|
||
// 计算对象边界框
|
||
const bounds = calculateBounds(objects);
|
||
if (!bounds.absoluteBounds) {
|
||
throw new Error("无法计算对象边界框");
|
||
}
|
||
|
||
// 创建临时画布用于渲染
|
||
const tempCanvas = new fabric.StaticCanvas();
|
||
const { absoluteBounds } = bounds;
|
||
|
||
tempCanvas.setDimensions({
|
||
width: Math.ceil(absoluteBounds.width),
|
||
height: Math.ceil(absoluteBounds.height),
|
||
});
|
||
|
||
// 调整对象位置并添加到临时画布
|
||
for (const obj of objects) {
|
||
const tempObj = await cloneObjectAsync(obj);
|
||
tempObj.set({
|
||
left: tempObj.left - absoluteBounds.left,
|
||
top: tempObj.top - absoluteBounds.top,
|
||
});
|
||
tempCanvas.add(tempObj);
|
||
}
|
||
|
||
// 渲染并获取DataURL
|
||
tempCanvas.renderAll();
|
||
const dataURL = tempCanvas.toDataURL({
|
||
format,
|
||
quality,
|
||
});
|
||
|
||
// 清理临时画布
|
||
tempCanvas.dispose();
|
||
|
||
return dataURL;
|
||
} catch (error) {
|
||
console.error("渲染对象为DataURL失败:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 渲染裁剪后的对象为DataURL
|
||
* 专门处理带有裁剪路径的对象渲染(备用方法)
|
||
*/
|
||
const renderClippedObjectsToDataURL = async (clippedObjects) => {
|
||
try {
|
||
console.log("🖼️ 渲染裁剪对象为DataURL");
|
||
|
||
// 计算所有裁剪对象的总边界框
|
||
let totalBounds = null;
|
||
|
||
for (const obj of clippedObjects) {
|
||
const objBounds = obj.getBoundingRect(true, true);
|
||
|
||
if (!totalBounds) {
|
||
totalBounds = { ...objBounds };
|
||
} else {
|
||
const right = Math.max(
|
||
totalBounds.left + totalBounds.width,
|
||
objBounds.left + objBounds.width
|
||
);
|
||
const bottom = Math.max(
|
||
totalBounds.top + totalBounds.height,
|
||
objBounds.top + objBounds.height
|
||
);
|
||
|
||
totalBounds.left = Math.min(totalBounds.left, objBounds.left);
|
||
totalBounds.top = Math.min(totalBounds.top, objBounds.top);
|
||
totalBounds.width = right - totalBounds.left;
|
||
totalBounds.height = bottom - totalBounds.top;
|
||
}
|
||
}
|
||
|
||
if (!totalBounds) {
|
||
throw new Error("无法计算对象边界框");
|
||
}
|
||
|
||
// 创建临时画布,使用高分辨率
|
||
const tempCanvas = new fabric.StaticCanvas();
|
||
const pixelRatio = window.devicePixelRatio || 1;
|
||
const scaleFactor = Math.max(2, pixelRatio);
|
||
|
||
const canvasWidth = Math.ceil(totalBounds.width * scaleFactor);
|
||
const canvasHeight = Math.ceil(totalBounds.height * scaleFactor);
|
||
|
||
tempCanvas.setDimensions({
|
||
width: canvasWidth,
|
||
height: canvasHeight,
|
||
});
|
||
|
||
// 调整对象位置并添加到临时画布
|
||
for (const obj of clippedObjects) {
|
||
const tempObj = await cloneObjectAsync(obj);
|
||
|
||
// 调整位置到画布坐标系
|
||
tempObj.set({
|
||
left: (tempObj.left - totalBounds.left) * scaleFactor,
|
||
top: (tempObj.top - totalBounds.top) * scaleFactor,
|
||
scaleX: (tempObj.scaleX || 1) * scaleFactor,
|
||
scaleY: (tempObj.scaleY || 1) * scaleFactor,
|
||
});
|
||
|
||
// 如果有裁剪路径,也需要调整裁剪路径
|
||
if (tempObj.clipPath) {
|
||
tempObj.clipPath.set({
|
||
scaleX: (tempObj.clipPath.scaleX || 1) * scaleFactor,
|
||
scaleY: (tempObj.clipPath.scaleY || 1) * scaleFactor,
|
||
});
|
||
}
|
||
|
||
tempCanvas.add(tempObj);
|
||
}
|
||
|
||
// 渲染画布
|
||
tempCanvas.renderAll();
|
||
|
||
// 获取DataURL
|
||
const dataURL = tempCanvas.toDataURL({
|
||
format: "png",
|
||
quality: 1.0,
|
||
multiplier: 1, // 已经通过尺寸处理了缩放
|
||
});
|
||
|
||
// 清理临时画布
|
||
tempCanvas.dispose();
|
||
|
||
console.log("✅ 裁剪对象渲染完成");
|
||
return dataURL;
|
||
} catch (error) {
|
||
console.error("渲染裁剪对象失败:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 兼容的离屏渲染方法(原有逻辑,作为备选)
|
||
*/
|
||
const createLegacyRasterization = async ({
|
||
canvas,
|
||
fabricObjects,
|
||
clippingObject,
|
||
scaleFactor,
|
||
quality,
|
||
format,
|
||
isReturenDataURL,
|
||
}) => {
|
||
console.log("⚠️ 使用兼容的离屏渲染方法");
|
||
|
||
// 这里保留原有的离屏渲染逻辑作为备选方案
|
||
const currentZoom = canvas.getZoom?.() || 1;
|
||
scaleFactor = Math.max(
|
||
scaleFactor || canvas?.getRetinaScaling?.(),
|
||
currentZoom
|
||
);
|
||
scaleFactor = Math.min(scaleFactor, 3);
|
||
|
||
const { absoluteBounds, relativeBounds } = calculateBounds(fabricObjects);
|
||
|
||
return await createOffscreenRasterization({
|
||
canvas,
|
||
objects: fabricObjects,
|
||
absoluteBounds,
|
||
relativeBounds,
|
||
scaleFactor,
|
||
clippingObject,
|
||
trimWhitespace: true,
|
||
trimPadding: 1,
|
||
quality,
|
||
format,
|
||
currentZoom,
|
||
isReturenDataURL,
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 计算对象的绝对边界框和相对边界框
|
||
* @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 ({
|
||
canvas,
|
||
objects,
|
||
absoluteBounds,
|
||
relativeBounds,
|
||
scaleFactor,
|
||
clippingObject,
|
||
trimWhitespace,
|
||
trimPadding,
|
||
quality,
|
||
format,
|
||
currentZoom,
|
||
isReturenDataURL,
|
||
}) => {
|
||
try {
|
||
// 创建离屏画布,使用绝对尺寸以保证高质量
|
||
const offscreenCanvas = new fabric.StaticCanvas();
|
||
|
||
// 如果有裁剪对象,使用裁剪对象的边界框
|
||
let renderBounds = absoluteBounds;
|
||
if (clippingObject) {
|
||
const clippingBounds = clippingObject.getBoundingRect(true, true);
|
||
console.log("🎯 使用裁剪对象边界框:", clippingBounds);
|
||
renderBounds = clippingBounds;
|
||
}
|
||
|
||
// 设置离屏画布尺寸,并应用高清倍数
|
||
const canvasWidth = Math.ceil(renderBounds.width);
|
||
const canvasHeight = Math.ceil(renderBounds.height);
|
||
|
||
offscreenCanvas.setDimensions({
|
||
width: canvasWidth,
|
||
height: canvasHeight,
|
||
});
|
||
|
||
console.log(
|
||
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
|
||
);
|
||
|
||
// 克隆对象到离屏画布
|
||
const clonedObjects = [];
|
||
for (const obj of objects) {
|
||
const clonedObj = await cloneObjectAsync(obj);
|
||
|
||
// 调整对象位置,相对于渲染边界框的左上角
|
||
clonedObj.set({
|
||
left: clonedObj.left - renderBounds.left,
|
||
top: clonedObj.top - renderBounds.top,
|
||
});
|
||
|
||
// 如果有裁剪对象,为每个对象设置裁剪路径
|
||
if (clippingObject) {
|
||
const clippingPath = await cloneObjectAsync(clippingObject);
|
||
clippingPath.set({
|
||
left: clippingPath.left - renderBounds.left,
|
||
top: clippingPath.top - renderBounds.top,
|
||
fill: "",
|
||
stroke: "",
|
||
absolutePositioned: true,
|
||
});
|
||
clonedObj.set({
|
||
clipPath: clippingPath,
|
||
});
|
||
}
|
||
|
||
clonedObjects.push(clonedObj);
|
||
offscreenCanvas.add(clonedObj);
|
||
}
|
||
|
||
// 渲染离屏画布
|
||
offscreenCanvas.renderAll();
|
||
|
||
// 生成图像数据
|
||
const dataURL = offscreenCanvas.toDataURL({
|
||
format,
|
||
quality,
|
||
multiplier: 1, // 已经通过画布尺寸处理了高清倍数
|
||
});
|
||
|
||
if (isReturenDataURL) {
|
||
return dataURL; // 如果需要返回DataURL
|
||
}
|
||
|
||
// 清理离屏画布
|
||
offscreenCanvas.dispose();
|
||
|
||
// 创建fabric.Image对象
|
||
const fabricImage = await createFabricImageFromDataURL(dataURL);
|
||
|
||
// 设置图像位置为裁剪区域的位置
|
||
fabricImage.set({
|
||
left: renderBounds.left + renderBounds.width / 2,
|
||
top: renderBounds.top + renderBounds.height / 2,
|
||
originX: "center",
|
||
originY: "center",
|
||
});
|
||
|
||
return fabricImage;
|
||
} catch (error) {
|
||
console.error("离屏栅格化失败:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 异步克隆fabric对象
|
||
* @param {fabric.Object} obj 要克隆的对象
|
||
* @returns {Promise<fabric.Object>} 克隆的对象
|
||
*/
|
||
const cloneObjectAsync = (obj) => {
|
||
return new Promise((resolve, reject) => {
|
||
obj.clone((cloned) => {
|
||
if (cloned) {
|
||
resolve(cloned);
|
||
} else {
|
||
reject(new Error("对象克隆失败"));
|
||
}
|
||
});
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 从DataURL创建fabric.Image对象
|
||
* @param {string} dataURL 图像数据URL
|
||
* @returns {Promise<fabric.Image>} fabric图像对象
|
||
*/
|
||
const createFabricImageFromDataURL = (dataURL) => {
|
||
return new Promise((resolve, reject) => {
|
||
fabric.Image.fromURL(dataURL, (img) => {
|
||
if (img) {
|
||
resolve(img);
|
||
} else {
|
||
reject(new Error("无法从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",
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 应用遮罩到画布(已弃用,使用clipPath方式)
|
||
* @param {fabric.Canvas} canvas 目标画布
|
||
* @param {fabric.Object} maskObject 遮罩对象
|
||
* @param {Object} bounds 边界框
|
||
*/
|
||
const applyMaskToCanvas = async (canvas, maskObject, bounds) => {
|
||
// 此方法已被clipPath方式替代
|
||
console.log("⚠️ applyMaskToCanvas已被clipPath方式替代");
|
||
};
|
||
|
||
export const getObjectsBounds = (fabricObjects) => {
|
||
const { absoluteBounds } = calculateBounds(fabricObjects);
|
||
return absoluteBounds;
|
||
};
|