feat: 修复画布部分bug

This commit is contained in:
bighuixiang
2025-06-23 00:40:45 +08:00
parent ea1480dd7c
commit 4da5f7d105
14 changed files with 718 additions and 198 deletions

View File

@@ -1,8 +1,7 @@
import { createRasterizedImage } from "../utils/SelectionToImage.js";
import { CompositeCommand } from "./Command.js";
import { AddLayerCommand, CreateImageLayerCommand } from "./LayerCommands.js";
import { ToolCommand } from "./ToolCommands.js";
import { CreateImageLayerCommand } from "./LayerCommands.js";
import { ClearSelectionCommand } from "./SelectionCommands.js";
import { createLayer, LayerType, OperationType } from "../utils/layerHelper.js";
import { fabric } from "fabric-with-all";
/**
@@ -27,7 +26,7 @@ export class LassoCutoutCommand extends CompositeCommand {
this.executedCommands = [];
// 高清截图选项
this.highResolutionEnabled = options.highResolutionEnabled !== false; // 默认启用
this.baseResolutionScale = options.baseResolutionScale || 4; // 基础分辨率倍数提高到4倍获得更清晰的图像
this.baseResolutionScale = options.baseResolutionScale || 2; // 基础分辨率倍数
}
async execute() {
@@ -48,18 +47,26 @@ export class LassoCutoutCommand extends CompositeCommand {
// 确定源图层
const sourceLayer = this.layerManager.getActiveLayer();
if (!sourceLayer || sourceLayer.fabricObjects.length === 0) {
if (!sourceLayer) {
console.error("无法执行套索抠图:源图层无效");
return false;
}
// 获取源图层的所有对象(包括子图层)
const sourceObjects = this._getLayerObjects(sourceLayer);
if (sourceObjects.length === 0) {
console.error("无法执行套索抠图:源图层没有可见对象");
return false;
}
// 获取选区边界信息用于后续定位
const selectionBounds = selectionObject.getBoundingRect(true, true);
// 执行在当前画布上的抠图操作
this.cutoutImageUrl = await this._performCutout(
sourceLayer,
selectionObject
// 使用createRasterizedImage执行抠图操作
this.cutoutImageUrl = await this._performCutoutWithRasterized(
sourceObjects,
selectionObject,
selectionBounds
);
if (!this.cutoutImageUrl) {
console.error("抠图失败");
@@ -143,7 +150,120 @@ export class LassoCutoutCommand extends CompositeCommand {
}
/**
* 在当前画布上执行抠图操作
* 获取图层的所有对象(包括子图层,从画布中查找真实对象)
* @param {Object} layer 图层对象
* @returns {Array} 真实的fabric对象数组
* @private
*/
_getLayerObjects(layer) {
const objects = [];
const canvasObjects = this.canvas.getObjects();
// 递归获取图层及其子图层的所有对象
const collectLayerObjects = (currentLayer) => {
// 处理图层的fabricObjects
if (
currentLayer.fabricObjects &&
Array.isArray(currentLayer.fabricObjects)
) {
currentLayer.fabricObjects.forEach((fabricRef) => {
if (fabricRef && fabricRef.id) {
// 从画布中查找真实的对象
const realObject = canvasObjects.find(
(obj) => obj.id === fabricRef.id
);
if (realObject && realObject.visible) {
objects.push(realObject);
}
}
});
}
// 处理单个fabricObject背景图层等
if (currentLayer.fabricObject && currentLayer.fabricObject.id) {
const realObject = canvasObjects.find(
(obj) => obj.id === currentLayer.fabricObject.id
);
if (realObject && realObject.visible) {
objects.push(realObject);
}
}
// 递归处理子图层
if (currentLayer.children && Array.isArray(currentLayer.children)) {
currentLayer.children.forEach((childLayer) => {
if (childLayer.visible !== false) {
// 只处理可见的子图层
collectLayerObjects(childLayer);
}
});
}
};
collectLayerObjects(layer);
console.log(`从图层 "${layer.name}" 收集到 ${objects.length} 个可见对象`);
return objects;
}
/**
* 使用createRasterizedImage执行抠图操作
* @param {Array} sourceObjects 源对象数组
* @param {Object} selectionObject 选区对象
* @param {Object} selectionBounds 选区边界
* @returns {String} 抠图结果的DataURL
* @private
*/
async _performCutoutWithRasterized(
sourceObjects,
selectionObject,
selectionBounds
) {
try {
console.log("=== 开始使用createRasterizedImage执行抠图 ===");
console.log(`源对象数量: ${sourceObjects.length}`);
console.log(`选区边界:`, selectionBounds);
// 确定缩放因子
let scaleFactor = this.baseResolutionScale;
if (this.highResolutionEnabled) {
const devicePixelRatio = window.devicePixelRatio || 1;
scaleFactor = Math.max(scaleFactor, devicePixelRatio * 1.5);
}
// 使用createRasterizedImage生成栅格化图像将选区作为裁剪路径
const rasterizedDataURL = await createRasterizedImage({
canvas: this.canvas,
fabricObjects: sourceObjects,
clipPath: selectionObject, // 使用选区作为裁剪路径而不是遮罩
trimWhitespace: true,
trimPadding: 2,
quality: 1.0,
format: "png",
scaleFactor: scaleFactor,
isReturenDataURL: true, // 返回DataURL
});
if (!rasterizedDataURL) {
throw new Error("栅格化生成图像失败");
}
console.log(`抠图完成,缩放因子: ${scaleFactor}x`);
return rasterizedDataURL;
} catch (error) {
console.error("使用createRasterizedImage执行抠图失败:", error);
// 如果createRasterizedImage失败回退到原始方法
console.log("回退到原始抠图方法...");
return await this._performCutout(
{ fabricObjects: sourceObjects },
selectionObject
);
}
}
/**
* 原始抠图方法(作为备用方案)
* @param {Object} sourceLayer 源图层
* @param {Object} selectionObject 选区对象
* @returns {String} 抠图结果的DataURL
@@ -151,13 +271,10 @@ export class LassoCutoutCommand extends CompositeCommand {
*/
async _performCutout(sourceLayer, selectionObject) {
try {
console.log("=== 开始在当前画布执行抠图 ===");
console.log("=== 使用原始方法执行抠图 ===");
// 获取选区边界
const selectionBounds = selectionObject.getBoundingRect(true, true);
console.log(
`选区边界: left=${selectionBounds.left}, top=${selectionBounds.top}, width=${selectionBounds.width}, height=${selectionBounds.height}`
);
// 保存画布当前状态
const originalActiveObject = this.canvas.getActiveObject();
@@ -175,7 +292,6 @@ export class LassoCutoutCommand extends CompositeCommand {
const visibleObjects = sourceLayer.fabricObjects.filter(
(obj) => obj.visible
);
console.log(`源图层可见对象数量: ${visibleObjects.length}`);
if (visibleObjects.length === 0) {
throw new Error("源图层没有可见对象");
@@ -184,36 +300,19 @@ export class LassoCutoutCommand extends CompositeCommand {
// 如果只有一个对象且已经是组,直接使用
if (visibleObjects.length === 1 && visibleObjects[0].type === "group") {
tempGroup = visibleObjects[0];
console.log("使用现有组对象");
} else {
// 创建临时组
console.log("创建临时组...");
// 记录原始对象的位置和状态,用于后续恢复
originalObjects = visibleObjects.map((obj) => ({
object: obj,
originalLeft: obj.left,
originalTop: obj.top,
originalAngle: obj.angle,
originalScaleX: obj.scaleX,
originalScaleY: obj.scaleY,
}));
// 不需要从画布移除原对象,直接创建组
// 克隆对象来创建组,避免影响原对象
const clonedObjects = [];
for (const obj of visibleObjects) {
const cloned = await this._cloneObject(obj);
clonedObjects.push(cloned);
}
// 创建组
tempGroup = new fabric.Group(clonedObjects, {
selectable: false,
evented: false,
});
// 添加组到画布
this.canvas.add(tempGroup);
}
@@ -227,7 +326,6 @@ export class LassoCutoutCommand extends CompositeCommand {
originY: "top",
});
// 应用裁剪路径到组
tempGroup.set({
clipPath: clipPath,
});
@@ -242,23 +340,14 @@ export class LassoCutoutCommand extends CompositeCommand {
height: selectionBounds.height,
};
// 设置高分辨率倍数,用于提高图像清晰度
// 设置高分辨率倍数
let highResolutionScale = 1;
if (this.highResolutionEnabled) {
// 结合设备像素比和配置的基础倍数,确保在所有设备上都有最佳效果
const devicePixelRatio = window.devicePixelRatio || 1;
// 使用更激进的缩放策略,确保高清晰度
highResolutionScale = Math.max(
this.baseResolutionScale,
devicePixelRatio * 2
);
console.log(
`设备像素比: ${devicePixelRatio}, 基础倍数: ${this.baseResolutionScale}, 最终放大倍数: ${highResolutionScale}`
);
} else {
console.log("高分辨率渲染已禁用使用1x倍数");
}
// 创建用于导出的高分辨率canvas
@@ -269,7 +358,6 @@ export class LassoCutoutCommand extends CompositeCommand {
colorSpace: "srgb",
});
// 设置canvas的实际像素尺寸放大倍数
const actualWidth = Math.round(
renderBounds.width * highResolutionScale
);
@@ -279,99 +367,38 @@ export class LassoCutoutCommand extends CompositeCommand {
exportCanvas.width = actualWidth;
exportCanvas.height = actualHeight;
// 设置canvas的显示尺寸CSS尺寸保持与选区一致
exportCanvas.style.width = renderBounds.width + "px";
exportCanvas.style.height = renderBounds.height + "px";
// 启用最高质量渲染设置
exportCtx.imageSmoothingEnabled = true;
exportCtx.imageSmoothingQuality = "high";
// 设置文本渲染质量
if (exportCtx.textRenderingOptimization) {
exportCtx.textRenderingOptimization = "optimizeQuality";
}
// 设置线条和图形的渲染质量
exportCtx.lineCap = "round";
exportCtx.lineJoin = "round";
exportCtx.miterLimit = 10;
// 设置画布背景为透明
exportCtx.clearRect(0, 0, actualWidth, actualHeight);
// 获取画布当前的变换矩阵
const vpt = this.canvas.viewportTransform;
const zoom = this.canvas.getZoom();
// 保存当前变换状态
exportCtx.save();
// 应用高分辨率缩放
exportCtx.scale(highResolutionScale, highResolutionScale);
// 应用偏移,只渲染选区部分
exportCtx.translate(-renderBounds.left, -renderBounds.top);
// 如果画布有缩放和平移,需要应用相应变换
if (zoom !== 1 || vpt[4] !== 0 || vpt[5] !== 0) {
exportCtx.transform(vpt[0], vpt[1], vpt[2], vpt[3], vpt[4], vpt[5]);
}
// 渲染被裁剪的组
tempGroup.render(exportCtx);
exportCtx.restore();
// 获取结果 - 使用最高质量设置
const dataUrl = exportCanvas.toDataURL("image/png", 1.0);
console.log(
`抠图完成,选区尺寸: ${renderBounds.width}x${renderBounds.height}, 实际渲染尺寸: ${actualWidth}x${actualHeight}, 放大倍数: ${highResolutionScale}x`
);
return dataUrl;
} finally {
// 清理和恢复
if (tempGroup) {
// 移除裁剪路径
tempGroup.set({ clipPath: null });
// 如果是我们创建的临时组,需要移除它
if (originalObjects.length > 0) {
this.canvas.remove(tempGroup);
// 恢复原始对象的状态(位置等信息保持不变)
originalObjects.forEach(
({
object,
originalLeft,
originalTop,
originalAngle,
originalScaleX,
originalScaleY,
}) => {
// 确保对象仍然在画布上且状态正确
if (!this.canvas.getObjects().includes(object)) {
this.canvas.add(object);
}
// 恢复原始变换状态(如果需要的话)
object.set({
left: originalLeft,
top: originalTop,
angle: originalAngle,
scaleX: originalScaleX,
scaleY: originalScaleY,
});
object.setCoords();
}
);
}
}
// 恢复画布状态
this.canvas.selection = originalSelection;
if (originalActiveObject) {
this.canvas.setActiveObject(originalActiveObject);
@@ -379,7 +406,7 @@ export class LassoCutoutCommand extends CompositeCommand {
this.canvas.renderAll();
}
} catch (error) {
console.error("在当前画布执行抠图失败:", error);
console.error("原始方法执行抠图失败:", error);
return null;
}
}
@@ -401,39 +428,14 @@ export class LassoCutoutCommand extends CompositeCommand {
return;
}
// 计算画布中心位置
const canvasCenter = this.canvas.getCenter();
// 使用选区的位置作为图像位置
let targetLeft = selectionBounds.left + selectionBounds.width / 2;
let targetTop = selectionBounds.top + selectionBounds.height / 2;
// 如果有选区边界信息,使用选区的原始位置和尺寸
let targetLeft = canvasCenter.left;
let targetTop = canvasCenter.top;
let targetWidth = img.width;
let targetHeight = img.height;
if (selectionBounds) {
// 使用选区的原始位置
targetLeft = selectionBounds.left + selectionBounds.width / 2;
targetTop = selectionBounds.top + selectionBounds.height / 2;
// 确保图像显示尺寸与选区尺寸一致
targetWidth = selectionBounds.width;
targetHeight = selectionBounds.height;
console.log(
`设置图像位置: left=${targetLeft}, top=${targetTop}, 尺寸: ${targetWidth}x${targetHeight}`
);
}
// 计算缩放比例以匹配目标尺寸
const scaleX = targetWidth / img.width;
const scaleY = targetHeight / img.height;
// 设置图像属性
// 设置图像属性,保持选区的原始尺寸
img.set({
left: targetLeft,
top: targetTop,
scaleX: scaleX,
scaleY: scaleY,
originX: "center",
originY: "center",
selectable: true,
@@ -455,6 +457,9 @@ export class LassoCutoutCommand extends CompositeCommand {
// 更新坐标
img.setCoords();
console.log(
`图像创建完成,位置: (${targetLeft}, ${targetTop}), 尺寸: ${img.width}x${img.height}`
);
resolve(img);
},
{