// 栅格化帮助 import { fabric } from "fabric-with-all"; import { createStaticCanvas } from "./canvasFactory"; /** * 创建栅格化图像 * 使用组对象方式,避免边界计算误差 * @returns {Promise} 栅格化后的图像对象 * @private */ export const createRasterizedImage = async ({ canvas, // 画布对象 必填 fabricObjects = [], // 要栅格化的对象列表 - 按顺序 必填 maskObject = null, // 用于裁剪的对象 - 可选 trimWhitespace = true, // 是否裁剪空白区域 trimPadding = 0, // 裁剪边距 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 ); console.log(`高清倍数: ${scaleFactor}, 当前缩放: ${currentZoom}`); // 使用组对象方式创建栅格化图像 const rasterizedImage = await createRasterizedImageWithGroup({ canvas, objects: fabricObjects, 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, originalZoom: currentZoom, }, }); console.log(`✅ 栅格化图像创建完成`); } return rasterizedImage; } catch (error) { console.error("创建栅格化图像失败:", error); throw new Error(`栅格化失败: ${error.message}`); } }; /** * 使用组对象方式创建栅格化图像 * @param {Object} options 渲染选项 * @returns {Promise} 栅格化后的图像对象 */ const createRasterizedImageWithGroup = async ({ canvas, objects, scaleFactor, maskObject, trimWhitespace, trimPadding, quality, format, currentZoom, isReturenDataURL, }) => { try { // 创建离屏画布 const offscreenCanvas = createStaticCanvas(); // 克隆所有对象 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, }); console.log( `🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}` ); // 调整组的位置,让它位于画布的左上角 group.set({ left: 0, top: 0, }); // 取消对象激活 group.set({ selectable: false, // 禁用组的选择 evented: false, // 禁用组的事件 }); // 将组添加到离屏画布 offscreenCanvas.add(group); // 设置离屏画布的缩放 offscreenCanvas.setZoom(scaleFactor); // 如果有遮罩对象,应用遮罩 if (maskObject) { await applyMaskToCanvas(offscreenCanvas, maskObject, groupBounds); } // 渲染离屏画布 offscreenCanvas.renderAll(); // 生成图像数据 const dataURL = offscreenCanvas.toDataURL({ format, quality, multiplier: 1, // 已经通过画布尺寸处理了高清倍数 }); if (isReturenDataURL) { // 清理离屏画布 offscreenCanvas.dispose(); return dataURL; // 如果需要返回DataURL } // 清理离屏画布 offscreenCanvas.dispose(); // 创建fabric.Image对象 const fabricImage = await createFabricImageFromDataURL(dataURL); // 设置图像的位置和缩放,使其与原始组的位置和大小匹配 fabricImage.set({ 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); 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 {fabric.Canvas} canvas 目标画布 * @param {fabric.Object} maskObject 遮罩对象 * @param {Object} bounds 边界框 */ const applyMaskToCanvas = async (canvas, maskObject, bounds) => { // 这里可以实现遮罩逻辑 // 例如使用canvas的clipPath或其他遮罩技术 console.log("应用遮罩功能待实现"); }; /** * 获取对象组的边界框 * @param {Array} fabricObjects fabric对象数组 * @returns {Object} 边界框信息 */ export const getObjectsBounds = (fabricObjects) => { 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; };