fix: 修复多个已知问题
This commit is contained in:
@@ -19,6 +19,7 @@ export const createRasterizedImage = async ({
|
||||
scaleFactor = 1, // 高清倍数 - 默认是画布的高清倍数
|
||||
isReturenDataURL = false, // 是否返回DataURL而不是fabric.Image对象
|
||||
preserveOriginalQuality = true, // 是否保持原始质量(新增)
|
||||
selectionManager = null, // 选区管理器,用于获取羽化值等设置
|
||||
} = {}) => {
|
||||
try {
|
||||
console.log(`📊 开始栅格化 ${fabricObjects.length} 个对象`);
|
||||
@@ -38,6 +39,7 @@ export const createRasterizedImage = async ({
|
||||
fabricObjects,
|
||||
clippingObject,
|
||||
isReturenDataURL,
|
||||
selectionManager, // 传递选区管理器
|
||||
});
|
||||
}
|
||||
|
||||
@@ -77,20 +79,36 @@ const createClippedObjects = async ({
|
||||
fabricObjects,
|
||||
clippingObject,
|
||||
isReturenDataURL,
|
||||
selectionManager = null, // 新增选区管理器参数
|
||||
}) => {
|
||||
try {
|
||||
console.log("🎯 使用新的裁剪方法创建对象");
|
||||
console.log("🎯 使用新的图像遮罩裁剪方法创建对象");
|
||||
|
||||
// 使用优化后的边界计算,确保包含描边区域
|
||||
const optimizedBounds = calculateOptimizedBounds(
|
||||
clippingObject,
|
||||
fabricObjects
|
||||
);
|
||||
console.log("📐 优化后的选区边界框:", optimizedBounds);
|
||||
|
||||
// 获取羽化值
|
||||
let featherAmount = 0;
|
||||
if (
|
||||
selectionManager &&
|
||||
typeof selectionManager.getFeatherAmount === "function"
|
||||
) {
|
||||
featherAmount = selectionManager.getFeatherAmount();
|
||||
console.log(`🌟 应用羽化效果: ${featherAmount}px`);
|
||||
}
|
||||
|
||||
// 获取选区边界框
|
||||
const selectionBounds = clippingObject.getBoundingRect(true);
|
||||
console.log("📐 选区边界框:", selectionBounds);
|
||||
// 方法1:如果只需要返回DataURL,使用画布裁剪方法
|
||||
if (isReturenDataURL) {
|
||||
return await createClippedDataURLByCanvas({
|
||||
canvas,
|
||||
fabricObjects,
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
selectionBounds: optimizedBounds, // 使用优化后的边界框
|
||||
featherAmount,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,40 +117,44 @@ const createClippedObjects = async ({
|
||||
canvas,
|
||||
fabricObjects,
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
selectionBounds: optimizedBounds, // 使用优化后的边界框
|
||||
featherAmount,
|
||||
});
|
||||
|
||||
// 将DataURL转换为fabric.Image对象
|
||||
const fabricImage = await createFabricImageFromDataURL(clippedDataURL);
|
||||
|
||||
// 使用fabric原生方法恢复到选区的原始大小和位置
|
||||
fabricImage.scaleToWidth(selectionBounds.width);
|
||||
fabricImage.scaleToHeight(selectionBounds.height);
|
||||
fabricImage.scaleToWidth(optimizedBounds.width);
|
||||
fabricImage.scaleToHeight(optimizedBounds.height);
|
||||
|
||||
// 设置到选区的原始位置(中心点)
|
||||
fabricImage.set({
|
||||
left: selectionBounds.left + selectionBounds.width / 2,
|
||||
top: selectionBounds.top + selectionBounds.height / 2,
|
||||
left: optimizedBounds.left + optimizedBounds.width / 2,
|
||||
top: optimizedBounds.top + optimizedBounds.height / 2,
|
||||
originX: "center",
|
||||
originY: "center",
|
||||
selectable: true,
|
||||
evented: true,
|
||||
hasControls: true,
|
||||
hasBorders: true,
|
||||
// hasControls: true,
|
||||
// hasBorders: true,
|
||||
custom: {
|
||||
type: "clipped",
|
||||
clippedAt: new Date().toISOString(),
|
||||
hasClipping: true,
|
||||
preservedQuality: true,
|
||||
originalBounds: selectionBounds,
|
||||
originalBounds: optimizedBounds, // 保存优化后的边界框
|
||||
restoredToOriginalSize: true,
|
||||
usedImageMask: true, // 标记使用了图像遮罩
|
||||
featherAmount: featherAmount,
|
||||
boundaryOptimized: true, // 标记使用了边界优化
|
||||
},
|
||||
});
|
||||
|
||||
// 更新坐标
|
||||
fabricImage.setCoords();
|
||||
|
||||
console.log("✅ 返回裁剪后的fabric对象,已恢复到原始大小和位置");
|
||||
console.log("✅ 返回裁剪后的fabric对象,已恢复到优化后的原始大小和位置");
|
||||
return fabricImage;
|
||||
} catch (error) {
|
||||
console.error("创建裁剪对象失败:", error);
|
||||
@@ -149,78 +171,66 @@ const createClippedDataURLByCanvas = async ({
|
||||
fabricObjects,
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
featherAmount = 0,
|
||||
}) => {
|
||||
try {
|
||||
console.log("🖼️ 使用画布裁剪方法生成DataURL");
|
||||
console.log("🖼️ 使用图像遮罩裁剪方法生成DataURL");
|
||||
|
||||
// 创建临时画布,尺寸与选区完全一致
|
||||
const tempCanvas = new fabric.StaticCanvas();
|
||||
// 使用优化后的边界计算,确保包含描边区域
|
||||
const optimizedBounds = calculateOptimizedBounds(
|
||||
clippingObject,
|
||||
fabricObjects
|
||||
);
|
||||
|
||||
// 使用高分辨率以保证质量
|
||||
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,
|
||||
});
|
||||
const canvasWidth = Math.ceil(optimizedBounds.width * qualityMultiplier);
|
||||
const canvasHeight = Math.ceil(optimizedBounds.height * qualityMultiplier);
|
||||
|
||||
console.log(
|
||||
`📏 临时画布尺寸: ${canvasWidth}x${canvasHeight} (质量倍数: ${qualityMultiplier})`
|
||||
`📏 优化后画布尺寸: ${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,
|
||||
console.log("🎯 边界框对比:", {
|
||||
original: selectionBounds,
|
||||
optimized: optimizedBounds,
|
||||
});
|
||||
|
||||
// 为整个画布设置裁剪路径
|
||||
tempCanvas.clipPath = clipPath;
|
||||
// 步骤1: 先将路径转换为遮罩图像(支持羽化)
|
||||
const maskImageDataURL =
|
||||
featherAmount > 0
|
||||
? await createAdvancedMaskImage({
|
||||
clippingObject,
|
||||
selectionBounds: optimizedBounds, // 使用优化后的边界框
|
||||
qualityMultiplier,
|
||||
featherAmount,
|
||||
})
|
||||
: await createMaskImageFromPath({
|
||||
clippingObject,
|
||||
selectionBounds: optimizedBounds, // 使用优化后的边界框
|
||||
qualityMultiplier,
|
||||
});
|
||||
|
||||
// 渲染画布
|
||||
tempCanvas.renderAll();
|
||||
|
||||
// 生成高质量DataURL
|
||||
const dataURL = tempCanvas.toDataURL({
|
||||
format: "png",
|
||||
quality: 1.0,
|
||||
multiplier: 1, // 已经通过尺寸处理了缩放
|
||||
// 步骤2: 渲染原始内容
|
||||
const contentImageDataURL = await renderContentToImage({
|
||||
fabricObjects,
|
||||
selectionBounds: optimizedBounds, // 使用优化后的边界框
|
||||
qualityMultiplier,
|
||||
});
|
||||
|
||||
// 清理临时画布
|
||||
tempCanvas.dispose();
|
||||
// 步骤3: 使用遮罩合成最终结果
|
||||
const clippedDataURL = await applyImageMask({
|
||||
contentImageDataURL,
|
||||
maskImageDataURL,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
});
|
||||
|
||||
console.log("✅ 画布裁剪完成,生成DataURL");
|
||||
return dataURL;
|
||||
console.log("✅ 图像遮罩裁剪完成,生成DataURL");
|
||||
return clippedDataURL;
|
||||
} catch (error) {
|
||||
console.error("画布裁剪失败:", error);
|
||||
console.error("图像遮罩裁剪失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -731,3 +741,477 @@ export const getObjectsBounds = (fabricObjects) => {
|
||||
const { absoluteBounds } = calculateBounds(fabricObjects);
|
||||
return absoluteBounds;
|
||||
};
|
||||
|
||||
/**
|
||||
* 将路径对象转换为遮罩图像
|
||||
* @param {Object} clippingObject 裁剪路径对象
|
||||
* @param {Object} selectionBounds 选区边界框
|
||||
* @param {Number} qualityMultiplier 质量倍数
|
||||
* @returns {Promise<String>} 遮罩图像的DataURL
|
||||
*/
|
||||
const createMaskImageFromPath = async ({
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier,
|
||||
}) => {
|
||||
try {
|
||||
console.log("🎭 创建路径遮罩图像");
|
||||
|
||||
// 创建专门用于渲染遮罩的画布
|
||||
const maskCanvas = new fabric.StaticCanvas();
|
||||
|
||||
const canvasWidth = Math.ceil(selectionBounds.width * qualityMultiplier);
|
||||
const canvasHeight = Math.ceil(selectionBounds.height * qualityMultiplier);
|
||||
|
||||
maskCanvas.setDimensions({
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
});
|
||||
|
||||
// 克隆路径对象并处理描边转填充
|
||||
const maskPath = await createSolidMaskPath(
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier
|
||||
);
|
||||
|
||||
// 添加路径到遮罩画布
|
||||
maskCanvas.add(maskPath);
|
||||
maskCanvas.renderAll();
|
||||
|
||||
// 生成遮罩图像
|
||||
const maskDataURL = maskCanvas.toDataURL({
|
||||
format: "png",
|
||||
quality: 1.0,
|
||||
multiplier: 1,
|
||||
});
|
||||
|
||||
// 清理遮罩画布
|
||||
maskCanvas.dispose();
|
||||
|
||||
console.log("✅ 遮罩图像创建完成");
|
||||
return maskDataURL;
|
||||
} catch (error) {
|
||||
console.error("创建遮罩图像失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染内容对象为图像
|
||||
* @param {Array} fabricObjects 要渲染的对象数组
|
||||
* @param {Object} selectionBounds 选区边界框
|
||||
* @param {Number} qualityMultiplier 质量倍数
|
||||
* @returns {Promise<String>} 内容图像的DataURL
|
||||
*/
|
||||
const renderContentToImage = async ({
|
||||
fabricObjects,
|
||||
selectionBounds,
|
||||
qualityMultiplier,
|
||||
}) => {
|
||||
try {
|
||||
console.log("🖼️ 渲染内容图像");
|
||||
|
||||
// 创建内容渲染画布
|
||||
const contentCanvas = new fabric.StaticCanvas();
|
||||
|
||||
const canvasWidth = Math.ceil(selectionBounds.width * qualityMultiplier);
|
||||
const canvasHeight = Math.ceil(selectionBounds.height * qualityMultiplier);
|
||||
|
||||
contentCanvas.setDimensions({
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
});
|
||||
|
||||
// 克隆并添加所有需要渲染的对象
|
||||
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,
|
||||
selectable: false,
|
||||
evented: false,
|
||||
});
|
||||
|
||||
contentCanvas.add(clonedObj);
|
||||
}
|
||||
|
||||
// 渲染内容画布
|
||||
contentCanvas.renderAll();
|
||||
|
||||
// 生成内容图像
|
||||
const contentDataURL = contentCanvas.toDataURL({
|
||||
format: "png",
|
||||
quality: 1.0,
|
||||
multiplier: 1,
|
||||
});
|
||||
|
||||
// 清理内容画布
|
||||
contentCanvas.dispose();
|
||||
|
||||
console.log("✅ 内容图像渲染完成");
|
||||
return contentDataURL;
|
||||
} catch (error) {
|
||||
console.error("渲染内容图像失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 使用遮罩图像对内容图像进行裁剪
|
||||
* @param {String} contentImageDataURL 内容图像DataURL
|
||||
* @param {String} maskImageDataURL 遮罩图像DataURL
|
||||
* @param {Number} canvasWidth 画布宽度
|
||||
* @param {Number} canvasHeight 画布高度
|
||||
* @returns {Promise<String>} 裁剪后的图像DataURL
|
||||
*/
|
||||
const applyImageMask = async ({
|
||||
contentImageDataURL,
|
||||
maskImageDataURL,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
}) => {
|
||||
try {
|
||||
console.log("🎯 应用图像遮罩");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// 创建用于合成的Canvas元素
|
||||
const compositeCanvas = document.createElement("canvas");
|
||||
const ctx = compositeCanvas.getContext("2d");
|
||||
|
||||
compositeCanvas.width = canvasWidth;
|
||||
compositeCanvas.height = canvasHeight;
|
||||
|
||||
// 加载内容图像
|
||||
const contentImg = new Image();
|
||||
contentImg.onload = () => {
|
||||
// 加载遮罩图像
|
||||
const maskImg = new Image();
|
||||
maskImg.onload = () => {
|
||||
try {
|
||||
// 先绘制内容图像
|
||||
ctx.drawImage(contentImg, 0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
// 设置合成模式为遮罩模式
|
||||
ctx.globalCompositeOperation = "destination-in";
|
||||
|
||||
// 绘制遮罩图像
|
||||
ctx.drawImage(maskImg, 0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
// 重置合成模式
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
|
||||
// 获取最终结果
|
||||
const resultDataURL = compositeCanvas.toDataURL("image/png", 1.0);
|
||||
|
||||
console.log("✅ 图像遮罩应用完成");
|
||||
resolve(resultDataURL);
|
||||
} catch (error) {
|
||||
console.error("合成图像失败:", error);
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
maskImg.onerror = () => {
|
||||
reject(new Error("加载遮罩图像失败"));
|
||||
};
|
||||
|
||||
maskImg.src = maskImageDataURL;
|
||||
};
|
||||
|
||||
contentImg.onerror = () => {
|
||||
reject(new Error("加载内容图像失败"));
|
||||
};
|
||||
|
||||
contentImg.src = contentImageDataURL;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("应用图像遮罩失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建带羽化效果的遮罩图像(高级版本)
|
||||
* @param {Object} clippingObject 裁剪路径对象
|
||||
* @param {Object} selectionBounds 选区边界框
|
||||
* @param {Number} qualityMultiplier 质量倍数
|
||||
* @param {Number} featherAmount 羽化值
|
||||
* @returns {Promise<String>} 遮罩图像的DataURL
|
||||
*/
|
||||
const createAdvancedMaskImage = async ({
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier,
|
||||
featherAmount = 0,
|
||||
}) => {
|
||||
try {
|
||||
console.log(`🎭 创建高级遮罩图像 (羽化: ${featherAmount})`);
|
||||
|
||||
// 创建专门用于渲染遮罩的画布
|
||||
const maskCanvas = new fabric.StaticCanvas();
|
||||
|
||||
const canvasWidth = Math.ceil(selectionBounds.width * qualityMultiplier);
|
||||
const canvasHeight = Math.ceil(selectionBounds.height * qualityMultiplier);
|
||||
|
||||
maskCanvas.setDimensions({
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
});
|
||||
|
||||
// 克隆路径对象并处理描边转填充
|
||||
const maskPath = await createSolidMaskPath(
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier
|
||||
);
|
||||
|
||||
// 如果有羽化值,添加模糊效果
|
||||
if (featherAmount > 0) {
|
||||
const adjustedFeather = featherAmount * qualityMultiplier;
|
||||
maskPath.shadow = new fabric.Shadow({
|
||||
color: "#ffffff",
|
||||
blur: adjustedFeather,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// 添加路径到遮罩画布
|
||||
maskCanvas.add(maskPath);
|
||||
maskCanvas.renderAll();
|
||||
|
||||
// 如果有羽化,需要进行后处理
|
||||
if (featherAmount > 0) {
|
||||
return await applyCanvasBlur(
|
||||
maskCanvas,
|
||||
featherAmount * qualityMultiplier
|
||||
);
|
||||
}
|
||||
|
||||
// 生成遮罩图像
|
||||
const maskDataURL = maskCanvas.toDataURL({
|
||||
format: "png",
|
||||
quality: 1.0,
|
||||
multiplier: 1,
|
||||
});
|
||||
|
||||
// 清理遮罩画布
|
||||
maskCanvas.dispose();
|
||||
|
||||
console.log("✅ 高级遮罩图像创建完成");
|
||||
return maskDataURL;
|
||||
} catch (error) {
|
||||
console.error("创建高级遮罩图像失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 对画布应用模糊效果
|
||||
* @param {fabric.StaticCanvas} canvas 要处理的画布
|
||||
* @param {Number} blurAmount 模糊值
|
||||
* @returns {Promise<String>} 处理后的DataURL
|
||||
*/
|
||||
const applyCanvasBlur = async (canvas, blurAmount) => {
|
||||
try {
|
||||
// 获取原始图像数据
|
||||
const originalDataURL = canvas.toDataURL({
|
||||
format: "png",
|
||||
quality: 1.0,
|
||||
multiplier: 1,
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
// 创建一个新的Canvas进行模糊处理
|
||||
const blurCanvas = document.createElement("canvas");
|
||||
const ctx = blurCanvas.getContext("2d");
|
||||
|
||||
blurCanvas.width = canvas.width;
|
||||
blurCanvas.height = canvas.height;
|
||||
|
||||
// 应用CSS滤镜模糊
|
||||
ctx.filter = `blur(${Math.max(1, blurAmount / 2)}px)`;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
// 重置滤镜
|
||||
ctx.filter = "none";
|
||||
|
||||
const blurredDataURL = blurCanvas.toDataURL("image/png", 1.0);
|
||||
resolve(blurredDataURL);
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
reject(new Error("处理模糊效果失败"));
|
||||
};
|
||||
|
||||
img.src = originalDataURL;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("应用画布模糊失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建实体遮罩路径(将描边转换为填充)
|
||||
* @param {Object} clippingObject 原始裁剪对象
|
||||
* @param {Object} selectionBounds 选区边界框
|
||||
* @param {Number} qualityMultiplier 质量倍数
|
||||
* @returns {Promise<fabric.Object>} 处理后的遮罩路径对象
|
||||
*/
|
||||
const createSolidMaskPath = async (
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier
|
||||
) => {
|
||||
try {
|
||||
console.log("🔧 创建实体遮罩路径,处理描边转填充");
|
||||
|
||||
// 克隆原始对象
|
||||
const maskPath = await cloneObjectAsync(clippingObject);
|
||||
|
||||
// 检查是否有描边需要处理
|
||||
const hasStroke = maskPath.stroke && maskPath.strokeWidth > 0;
|
||||
|
||||
if (hasStroke) {
|
||||
console.log(
|
||||
`📏 检测到描边: ${maskPath.stroke}, 宽度: ${maskPath.strokeWidth}`
|
||||
);
|
||||
|
||||
// 对于有描边的路径,我们需要更精确的处理
|
||||
const strokeWidth = maskPath.strokeWidth;
|
||||
|
||||
// 方法1: 如果是简单的几何形状(矩形、圆形等),可以通过调整尺寸来补偿描边
|
||||
if (
|
||||
maskPath.type === "rect" ||
|
||||
maskPath.type === "circle" ||
|
||||
maskPath.type === "ellipse"
|
||||
) {
|
||||
// 对于矩形和椭圆,增加宽高来包含描边
|
||||
const strokeOffset = strokeWidth;
|
||||
|
||||
maskPath.set({
|
||||
left:
|
||||
(maskPath.left - selectionBounds.left - strokeOffset / 2) *
|
||||
qualityMultiplier,
|
||||
top:
|
||||
(maskPath.top - selectionBounds.top - strokeOffset / 2) *
|
||||
qualityMultiplier,
|
||||
scaleX: (maskPath.scaleX || 1) * qualityMultiplier,
|
||||
scaleY: (maskPath.scaleY || 1) * qualityMultiplier,
|
||||
width: (maskPath.width || 0) + strokeOffset,
|
||||
height: (maskPath.height || 0) + strokeOffset,
|
||||
fill: "#ffffff",
|
||||
stroke: "",
|
||||
strokeWidth: 0,
|
||||
selectable: false,
|
||||
evented: false,
|
||||
});
|
||||
} else {
|
||||
// 对于复杂路径,使用缩放方式来近似包含描边区域
|
||||
const pathBounds = maskPath.getBoundingRect(true, true);
|
||||
const minDimension = Math.min(pathBounds.width, pathBounds.height);
|
||||
const expandRatio = 1 + (strokeWidth * 2) / minDimension;
|
||||
const strokeOffset = strokeWidth / 2;
|
||||
|
||||
maskPath.set({
|
||||
left:
|
||||
(maskPath.left - selectionBounds.left - strokeOffset) *
|
||||
qualityMultiplier,
|
||||
top:
|
||||
(maskPath.top - selectionBounds.top - strokeOffset) *
|
||||
qualityMultiplier,
|
||||
scaleX: (maskPath.scaleX || 1) * qualityMultiplier * expandRatio,
|
||||
scaleY: (maskPath.scaleY || 1) * qualityMultiplier * expandRatio,
|
||||
fill: "#ffffff",
|
||||
stroke: "",
|
||||
strokeWidth: 0,
|
||||
selectable: false,
|
||||
evented: false,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`✅ 描边已转换为填充,类型: ${maskPath.type}`);
|
||||
} else {
|
||||
// 没有描边,直接处理位置和缩放
|
||||
maskPath.set({
|
||||
left: (maskPath.left - selectionBounds.left) * qualityMultiplier,
|
||||
top: (maskPath.top - selectionBounds.top) * qualityMultiplier,
|
||||
scaleX: (maskPath.scaleX || 1) * qualityMultiplier,
|
||||
scaleY: (maskPath.scaleY || 1) * qualityMultiplier,
|
||||
fill: "#ffffff", // 白色表示可见区域
|
||||
stroke: "", // 确保没有描边
|
||||
strokeWidth: 0,
|
||||
selectable: false,
|
||||
evented: false,
|
||||
});
|
||||
}
|
||||
|
||||
// 确保对象在画布中心正确对齐
|
||||
maskPath.setCoords();
|
||||
|
||||
return maskPath;
|
||||
} catch (error) {
|
||||
console.error("创建实体遮罩路径失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 优化边界计算,确保遮罩和内容对齐
|
||||
* @param {Object} clippingObject 裁剪对象
|
||||
* @param {Array} fabricObjects 内容对象数组
|
||||
* @returns {Object} 优化后的边界框信息
|
||||
*/
|
||||
const calculateOptimizedBounds = (clippingObject, fabricObjects) => {
|
||||
try {
|
||||
console.log("📐 计算优化后的边界框");
|
||||
|
||||
// 获取裁剪对象的边界框(包含描边)
|
||||
const clippingBounds = clippingObject.getBoundingRect(true, true);
|
||||
|
||||
// 如果有描边,需要调整边界框
|
||||
if (clippingObject.stroke && clippingObject.strokeWidth > 0) {
|
||||
const strokeWidth = clippingObject.strokeWidth;
|
||||
const halfStroke = strokeWidth / 2;
|
||||
|
||||
// 扩展边界框以包含完整的描边区域
|
||||
clippingBounds.left -= halfStroke;
|
||||
clippingBounds.top -= halfStroke;
|
||||
clippingBounds.width += strokeWidth;
|
||||
clippingBounds.height += strokeWidth;
|
||||
|
||||
console.log(`🖊️ 调整描边边界框,描边宽度: ${strokeWidth}`);
|
||||
}
|
||||
|
||||
// 计算内容对象的边界框
|
||||
const contentBounds = calculateBounds(fabricObjects);
|
||||
|
||||
// 使用裁剪边界框作为最终的选区边界框
|
||||
const optimizedBounds = {
|
||||
...clippingBounds,
|
||||
// 确保边界框不为负数或零
|
||||
width: Math.max(1, clippingBounds.width),
|
||||
height: Math.max(1, clippingBounds.height),
|
||||
};
|
||||
|
||||
console.log("✅ 边界框优化完成", {
|
||||
original: clippingObject.getBoundingRect(true, true),
|
||||
optimized: optimizedBounds,
|
||||
hasStroke: !!(clippingObject.stroke && clippingObject.strokeWidth > 0),
|
||||
});
|
||||
|
||||
return optimizedBounds;
|
||||
} catch (error) {
|
||||
console.error("计算优化边界框失败:", error);
|
||||
// 返回原始计算方式作为备选
|
||||
return clippingObject.getBoundingRect(true, true);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user