feat: 完善选区功能,新增组图层自由编辑
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user