feat: 完善选区功能,新增组图层自由编辑

This commit is contained in:
bighuixiang
2025-07-08 01:08:45 +08:00
parent 0615ab31f9
commit 5cc93aeba4
7 changed files with 1751 additions and 161 deletions

View File

@@ -18,9 +18,15 @@ export const createRasterizedImage = async ({
scaleFactor = 2, // 高清倍数 - 默认是画布的高清倍数
isReturenDataURL = false, // 是否返回DataURL而不是fabric.Image对象
isThumbnail = false, // 是否为缩略图
preserveOriginalQuality = true, // 是否保持原始质量
isGroupWithMask = false, // 是否为带遮罩的组图层
} = {}) => {
try {
console.log(`📊 开始栅格化 ${fabricObjects.length} 个对象`);
console.log(
`📊 开始栅格化 ${fabricObjects.length} 个对象${
maskObject ? "(带遮罩)" : ""
}`
);
// 确保有对象需要栅格化
if (fabricObjects.length === 0) {
@@ -39,7 +45,50 @@ export const createRasterizedImage = async ({
console.log(`高清倍数: ${scaleFactor}, 当前缩放: ${currentZoom}`);
// 使用组对象方式创建栅格化图像
// 如果有遮罩且保持原始质量,使用高质量的遮罩处理方法
if (maskObject && preserveOriginalQuality) {
const rasterizedImage = await createRasterizedImageWithMask({
canvas,
objects: fabricObjects,
maskObject,
scaleFactor,
quality,
format,
currentZoom,
isReturenDataURL,
isGroupWithMask,
});
if (!rasterizedImage) {
console.warn("⚠️ 带遮罩的栅格化图像创建失败,返回空图像");
return null;
}
if (isReturenDataURL) {
console.log("✅ 带遮罩的栅格化图像创建成功返回DataURL");
return rasterizedImage;
}
// 设置栅格化图像的属性
rasterizedImage.set({
selectable: true,
evented: true,
hasControls: true,
hasBorders: true,
custom: {
type: "rasterized",
rasterizedAt: new Date().toISOString(),
objectCount: fabricObjects.length,
originalZoom: currentZoom,
hasMask: true,
},
});
console.log(`✅ 带遮罩的栅格化图像创建完成`);
return rasterizedImage;
}
// 使用原有的组对象方式创建栅格化图像
const rasterizedImage = await createRasterizedImageWithGroup({
canvas,
objects: fabricObjects,
@@ -60,7 +109,7 @@ export const createRasterizedImage = async ({
if (isReturenDataURL) {
console.log("✅ 栅格化图像创建成功返回DataURL");
return rasterizedImage; // 返回DataURL
return rasterizedImage;
}
// 设置栅格化图像的属性
@@ -117,6 +166,7 @@ const createRasterizedImageWithGroup = async ({
select: false,
evented: false,
hasControls: false,
clipPath: null, // 确保克隆对象没有clipPath
});
clonedObjects.push(clonedObj);
}
@@ -207,6 +257,144 @@ const createRasterizedImageWithGroup = async ({
}
};
/**
* 使用遮罩创建栅格化图像 - 专门处理带遮罩的组图层
* 基于遮罩的位置和大小来裁剪内容,确保正确的定位
* @param {Object} options 渲染选项
* @returns {Promise<fabric.Image>} 栅格化后的图像对象
*/
const createRasterizedImageWithMask = async ({
canvas,
objects,
maskObject,
scaleFactor,
quality,
format,
currentZoom,
isReturenDataURL,
isGroupWithMask,
}) => {
try {
console.log("🎭 使用遮罩创建栅格化图像");
// 获取遮罩的边界框,这将作为最终图像的边界
const maskBounds = maskObject.getBoundingRect(true, true);
console.log("📏 遮罩边界框:", maskBounds);
// 克隆所有对象,并清除它们的遮罩,避免重复应用
const clonedObjects = [];
for (const obj of objects) {
const clonedObj = await cloneObjectAsync(obj);
clonedObj.set({
select: false,
evented: false,
hasControls: false,
clipPath: null, // 清除clipPath避免重复应用遮罩
});
clonedObjects.push(clonedObj);
}
// 克隆遮罩对象用于裁剪
const clonedMask = await cloneObjectAsync(maskObject);
clonedMask.set({
select: false,
evented: false,
hasControls: false,
absolutePositioned: false, // 设置为绝对定位
// fill: "#ffffff", // 遮罩使用白色
// stroke: "", // 确保没有描边
// strokeWidth: 0,
});
clonedMask.clipPath = null; // 确保克隆的遮罩没有clipPath
// 创建离屏画布,使用遮罩的边界大小
const offscreenCanvas = createStaticCanvas();
const canvasWidth = Math.ceil(maskBounds.width * scaleFactor);
const canvasHeight = Math.ceil(maskBounds.height * scaleFactor);
offscreenCanvas.setDimensions({
width: canvasWidth,
height: canvasHeight,
});
console.log(
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
);
// 调整对象位置,相对于遮罩边界重新定位
clonedObjects.forEach((obj) => {
obj.set({
left: (obj.left - maskBounds.left) * scaleFactor,
top: (obj.top - maskBounds.top) * scaleFactor,
scaleX: (obj.scaleX || 1) * scaleFactor,
scaleY: (obj.scaleY || 1) * scaleFactor,
});
obj.setCoords();
});
// 调整遮罩位置
clonedMask.set({
left: 0, // 遮罩从画布左上角开始
top: 0,
scaleX: (clonedMask.scaleX || 1) * scaleFactor,
scaleY: (clonedMask.scaleY || 1) * scaleFactor,
originX: "left",
originY: "top",
absolutePositioned: true, // 设置为绝对定位
});
clonedMask.setCoords();
// 添加所有对象到离屏画布
clonedObjects.forEach((obj) => {
offscreenCanvas.add(obj);
});
// 使用遮罩作为画布的clipPath来裁剪内容
offscreenCanvas.clipPath = clonedMask;
// 渲染离屏画布
offscreenCanvas.renderAll();
// 生成图像数据
const dataURL = offscreenCanvas.toDataURL({
format,
quality,
multiplier: 1, // 已经通过画布尺寸和对象缩放处理了高清倍数
});
if (isReturenDataURL) {
// 清理离屏画布
offscreenCanvas.dispose();
return dataURL;
}
// 清理离屏画布
offscreenCanvas.dispose();
// 创建fabric.Image对象
const fabricImage = await createFabricImageFromDataURL(dataURL);
// 设置图像的位置,使其与原始遮罩的位置匹配
fabricImage.set({
scaleX: 1 / scaleFactor, // 由于我们生成的图像是高清版本,需要缩放回原始大小
scaleY: 1 / scaleFactor,
left: maskBounds.left + maskBounds.width / 2, // 设置为遮罩中心点
top: maskBounds.top + maskBounds.height / 2, // 设置为遮罩中心点
originX: "center",
originY: "center",
});
// 确保图像位置正确
fabricImage.setCoords();
console.log("✅ 带遮罩的栅格化图像创建完成");
return fabricImage;
} catch (error) {
console.error("带遮罩的栅格化失败:", error);
throw error;
}
};
/**
* 异步克隆fabric对象
* @param {fabric.Object} obj 要克隆的对象
@@ -248,9 +436,40 @@ const createFabricImageFromDataURL = (dataURL) => {
* @param {Object} bounds 边界框
*/
const applyMaskToCanvas = async (canvas, maskObject, bounds) => {
// 这里可以实现遮罩逻辑
// 例如使用canvas的clipPath或其他遮罩技术
console.log("应用遮罩功能待实现");
if (!maskObject) {
console.log("没有遮罩对象,跳过遮罩应用");
return;
}
try {
console.log("🎭 应用遮罩到画布");
// 克隆遮罩对象,避免影响原对象
const clonedMask = await cloneObjectAsync(maskObject);
// 设置遮罩属性
clonedMask.set({
fill: "#ffffff", // 遮罩使用白色
stroke: "", // 确保没有描边
strokeWidth: 0,
selectable: false,
evented: false,
absolutePositioned: true, // 设置为绝对定位
});
// 调整遮罩位置,相对于画布边界重新定位
clonedMask.set({
left: clonedMask.left - bounds.left,
top: clonedMask.top - bounds.top,
});
// 将遮罩设置为画布的clipPath
canvas.clipPath = clonedMask;
console.log("✅ 遮罩应用完成");
} catch (error) {
console.error("应用遮罩失败:", error);
}
};
/**