// 栅格化帮助 import { fabric } from "fabric-with-all"; /** * 创建栅格化图像 * 使用增强版栅格化方法,不受原始画布变换影响 * @returns {Promise} 栅格化后的图像对象 * @private */ export const createRasterizedImage = async ({ canvas, // 画布对象 必填 fabricObjects = [], // 要栅格化的对象列表 - 按顺序 必填 maskObject = null, // 用于裁剪的对象 - 可选 trimWhitespace = true, // 是否裁剪空白区域 trimPadding = 1, // 裁剪边距 quality = 1.0, // 图像质量 format = "png", // 图像格式 scaleFactor = 2, // 高清倍数 - 默认是画布的高清倍数 isReturenDataURL = false, // 是否返回DataURL而不是fabric.Image对象 } = {}) => { try { console.log(`📊 开始栅格化 ${fabricObjects.length} 个对象`); // 确保有对象需要栅格化 if (fabricObjects.length === 0) { console.warn("⚠️ 没有对象需要栅格化,返回空图像"); return null; } // 高清倍数 const currentZoom = canvas.getZoom?.() || 1; scaleFactor = Math.max( scaleFactor || canvas?.getRetinaScaling?.(), 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({ canvas, objects: fabricObjects, absoluteBounds, relativeBounds, scaleFactor, maskObject, trimWhitespace, trimPadding, quality, format, currentZoom, isReturenDataURL, }); if (!rasterizedImage) { console.warn("⚠️ 栅格化图像创建失败,返回空图像"); return null; } if (isReturenDataURL) { console.log("✅ 栅格化图像创建成功,返回DataURL"); return rasterizedImage; // 返回DataURL } // 设置栅格化图像的属性 if (rasterizedImage) { rasterizedImage.set({ selectable: true, evented: true, hasControls: true, hasBorders: true, custom: { type: "rasterized", rasterizedAt: new Date().toISOString(), objectCount: fabricObjects.length, absoluteBounds, relativeBounds, originalZoom: currentZoom, }, }); console.log(`✅ 栅格化图像创建完成`); } return rasterizedImage; } catch (error) { console.error("创建栅格化图像失败:", error); throw new Error(`栅格化失败: ${error.message}`); } }; /** * 计算对象的绝对边界框和相对边界框 * @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} 栅格化后的图像对象 */ const createOffscreenRasterization = async ({ canvas, objects, absoluteBounds, relativeBounds, scaleFactor, maskObject, trimWhitespace, trimPadding, quality, format, currentZoom, isReturenDataURL, }) => { try { // 创建离屏画布,使用绝对尺寸以保证高质量 const offscreenCanvas = new fabric.Canvas(); // 设置离屏画布尺寸为绝对边界框大小,并应用高清倍数 const canvasWidth = Math.ceil(absoluteBounds.width * scaleFactor); const canvasHeight = Math.ceil(absoluteBounds.height * scaleFactor); offscreenCanvas.setDimensions({ width: canvasWidth, height: canvasHeight, }); // 设置离屏画布的缩放,确保对象以原始尺寸渲染 // offscreenCanvas.setZoom(scaleFactor); console.log( `🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}` ); // 克隆对象到离屏画布 const clonedObjects = []; for (const obj of objects) { const clonedObj = await cloneObjectAsync(obj); // 调整对象位置,相对于绝对边界框的左上角 // const absoluteObjBounds = obj.getBoundingRect(true, true); clonedObj.set({ left: clonedObj.left - absoluteBounds.left, top: clonedObj.top - absoluteBounds.top, }); clonedObjects.push(clonedObj); offscreenCanvas.add(clonedObj); } // 渲染离屏画布 offscreenCanvas.renderAll(); // 如果有遮罩对象,应用遮罩 if (maskObject) { await applyMaskToCanvas(offscreenCanvas, maskObject, absoluteBounds); } // 生成图像数据 const dataURL = offscreenCanvas.toDataURL({ format, quality, multiplier: 1, // 已经通过画布尺寸处理了高清倍数 }); if (isReturenDataURL) { return dataURL; // 如果需要返回DataURL } // 清理离屏画布 offscreenCanvas.dispose(); // 创建fabric.Image对象 const fabricImage = await createFabricImageFromDataURL(dataURL); // // 应用变换到fabric图像 fabricImage.set({ ...absoluteBounds, }); return fabricImage; } catch (error) { console.error("离屏栅格化失败:", error); throw error; } }; /** * 异步克隆fabric对象 * @param {fabric.Object} obj 要克隆的对象 * @returns {Promise} 克隆的对象 */ 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图像对象 */ 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", }; }; /** * 应用遮罩到画布(如果需要) * @param {fabric.Canvas} canvas 目标画布 * @param {fabric.Object} maskObject 遮罩对象 * @param {Object} bounds 边界框 */ const applyMaskToCanvas = async (canvas, maskObject, bounds) => { // 这里可以实现遮罩逻辑 // 例如使用canvas的clipPath或其他遮罩技术 console.log("应用遮罩功能待实现"); }; export const getObjectsBounds = (fabricObjects) => { const { absoluteBounds } = calculateBounds(fabricObjects); return absoluteBounds; };