Files
aida_front/src/component/Canvas/CanvasEditor/managers/ExportManager.js
2026-04-15 11:37:11 +08:00

1092 lines
31 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { fabric } from "fabric-with-all";
import { findObjectById } from "../utils/helper";
import { createRasterizedImage } from "../utils/selectionToImage";
import { OperationType, SpecialLayerId } from "../utils/layerHelper";
/**
* 图片导出管理器
* 负责处理画布的图片导出功能,支持多种导出选项和图层过滤
*/
export class ExportManager {
constructor(canvasManager, layerManager) {
this.canvasManager = canvasManager;
this.layerManager = layerManager;
this.canvas = canvasManager.canvas;
}
/**
* 导出图片
* @param {Object} options 导出选项
* @param {Boolean} options.isContainBg 是否包含背景图层
* @param {Boolean} options.isContainFixed 是否包含固定图层
* @param {Boolean} options.isContainFixedOther 是否包含其他固定图层
* @param {Boolean} options.isContainNormalLayer 是否包含普通图层
* @param {Boolean} options.isCropByBg 是否使用背景大小裁剪
* @param {String} options.layerId 导出具体图层ID
* @param {Array} options.layerIdArray 导出多个图层ID数组
* @param {Array} options.layerIdArray2 导出多个图层ID数组2
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
* @param {Boolean} options.restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
* @param {Boolean} options.isEnhanceImg 是否是增强图片
* @param {Array} options.excludedLayers 排除的图层ID数组
* @returns {String} 导出的图片数据URL
*/
async exportImage(options = {}) {
const {
isContainBg = false,
isContainFixed = false,
isContainFixedOther = false, // 是否包含其他固定图层
isContainNormalLayer = true, // 是否包含普通图层
isCropByBg = false, // 是否使用背景大小裁剪
layerId = "",
layerIdArray = [],
layerIdArray2 = null,
expPicType = "png",
restoreOpacityInRedGreen = true,
isEnhanceImg, // 是否是增强图片
excludedLayers = [], // 排除的图层ID数组
} = options;
try {
// 检查是否为红绿图模式
const isRedGreenMode = this.layerManager?.isInRedGreenMode?.() || false;
// 如果指定了具体图层ID导出指定图层
if (layerId) {
return this._exportSpecificLayer(
layerId,
expPicType,
isRedGreenMode,
restoreOpacityInRedGreen,
isCropByBg,
isEnhanceImg, // 是否是增强图片
);
}
// 如果指定了多个图层ID导出多个图层
if (layerIdArray && layerIdArray.length > 0) {
return this._exportMultipleLayers(
layerIdArray,
expPicType,
isContainBg,
isContainFixed,
isContainFixedOther, // 是否包含其他固定图层
isContainNormalLayer, // 是否包含普通图层
isRedGreenMode,
restoreOpacityInRedGreen,
isCropByBg,
isEnhanceImg, // 是否是增强图片
);
}
// 默认导出所有可见图层
return this._exportAllLayers(
expPicType,
isContainBg,
isContainFixed,
isContainFixedOther, // 是否包含其他固定图层
isContainNormalLayer, // 是否包含普通图层
isRedGreenMode,
restoreOpacityInRedGreen,
isCropByBg,
isEnhanceImg, // 是否是增强图片
layerIdArray2,
excludedLayers, // 排除的图层ID数组
);
} catch (error) {
console.error("导出图片失败:", error);
throw new Error(`图片导出失败: ${error.message}`);
}
}
/**
* 导出指定单个图层
* @param {String} layerId 图层ID
* @param {String} expPicType 导出类型
* @param {Boolean} isRedGreenMode 是否为红绿图模式
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
* @param {Boolean} isCropByBg 是否使用背景大小裁剪
* @param {Boolean} isEnhanceImg 是否是增强图片
* @returns {String} 图片数据URL
* @private
*/
async _exportSpecificLayer(
layerId,
expPicType,
isRedGreenMode,
restoreOpacityInRedGreen,
isCropByBg, // 是否使用背景大小裁剪
isEnhanceImg, // 是否是增强图片
) {
if (!this.layerManager) {
throw new Error("图层管理器未初始化");
}
const layer = this._getLayerById(layerId);
if (!layer) {
throw new Error(`未找到ID为 ${layerId} 的图层`);
}
if (!layer.visible) {
console.warn(`图层 ${layer.name} 不可见,将导出空白图片`);
}
// 收集所有需要导出的对象
const objectsToExport = this._collectObjectsFromLayer(layer);
if (objectsToExport.length === 0) {
console.warn(`图层 ${layer.name} 没有可导出的对象`);
return this._generateEmptyImage(expPicType);
}
// 红绿图模式下使用固定尺寸和裁剪
if (isRedGreenMode) {
return this._exportWithRedGreenMode(
objectsToExport,
expPicType,
restoreOpacityInRedGreen,
);
}
// 普通模式使用画布尺寸
return await this._exportWithCanvasSize(
objectsToExport,
expPicType,
restoreOpacityInRedGreen,
isCropByBg, // 是否使用背景大小裁剪
isEnhanceImg, // 是否是增强图片
);
}
/**
* 导出多个指定图层
* @param {Array} layerIdArray 图层ID数组
* @param {String} expPicType 导出类型
* @param {Boolean} isContainBg 是否包含背景图层
* @param {Boolean} isContainFixed 是否包含固定图层
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
* @param {Boolean} isContainNormalLayer 是否包含普通图层
* @param {Boolean} isRedGreenMode 是否为红绿图模式
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
* @param {Boolean} isCropByBg 是否使用背景大小裁剪
* @param {Boolean} isEnhanceImg 是否是增强图片
* @returns {String} 图片数据URL
* @private
*/
async _exportMultipleLayers(
layerIdArray,
expPicType,
isContainBg,
isContainFixed,
isContainFixedOther, // 是否包含其他固定图层
isContainNormalLayer = true, // 是否包含普通图层
isRedGreenMode,
restoreOpacityInRedGreen,
isCropByBg, // 是否使用背景大小裁剪
isEnhanceImg, // 是否是增强图片
) {
if (!this.layerManager) {
throw new Error("图层管理器未初始化");
}
// 按图层顺序收集对象(从底到顶)
const objectsToExport = this._collectObjectsByLayerOrder(
layerIdArray,
isContainBg,
isContainFixed,
isContainFixedOther, // 是否包含其他固定图层
isContainNormalLayer, // 是否包含普通图层
);
if (objectsToExport.length === 0) {
console.warn("没有可导出的对象");
return this._generateEmptyImage(expPicType);
}
// 红绿图模式下使用固定尺寸和裁剪
if (isRedGreenMode) {
return this._exportWithRedGreenMode(
objectsToExport,
expPicType,
restoreOpacityInRedGreen
);
}
// 普通模式使用画布尺寸
return await this._exportWithCanvasSize(
objectsToExport,
expPicType,
restoreOpacityInRedGreen,
isCropByBg, // 是否使用背景大小裁剪
isEnhanceImg, // 是否是增强图片
);
}
/**
* 导出所有图层
* @param {String} expPicType 导出类型
* @param {Boolean} isContainBg 是否包含背景图层
* @param {Boolean} isContainFixed 是否包含固定图层
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
* @param {Boolean} isContainNormalLayer 是否包含普通图层
* @param {Boolean} isRedGreenMode 是否为红绿图模式
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
* @param {Boolean} isCropByBg 是否使用背景大小裁剪
* @param {Boolean} isEnhanceImg 是否是增强图片
* @param {Array} layerIdArray 导出多个图层ID数组2
* @returns {String} 图片数据URL
* @private
*/
async _exportAllLayers(
expPicType,
isContainBg,
isContainFixed,
isContainFixedOther, // 是否包含其他固定图层
isContainNormalLayer, // 是否包含普通图层
isRedGreenMode,
restoreOpacityInRedGreen,
isCropByBg, // 是否使用背景大小裁剪
isEnhanceImg, // 是否是增强图片
layerIdArray, // 导出所有图层
excludedLayers, // 排除的图层ID数组
) {
// 按图层顺序收集对象(从底到顶)
const objectsToExport = this._collectObjectsByLayerOrder(
layerIdArray, // 导出所有图层
isContainBg,
isContainFixed,
isContainFixedOther, // 是否包含其他固定图层
isContainNormalLayer, // 是否包含普通图层
excludedLayers,
);
if (objectsToExport.length === 0) {
console.warn("没有可导出的对象");
return this._generateEmptyImage(expPicType);
}
// 红绿图模式下使用固定尺寸和裁剪
if (isRedGreenMode) {
return this._exportWithRedGreenMode(
objectsToExport,
expPicType,
restoreOpacityInRedGreen
);
}
let canvasClipPath = this.canvas.clipPath;
if (isCropByBg) {
const cropWidth =
this.canvasManager?.canvasWidth?.value ||
this.canvas?.canvasWidth ||
this.canvas.width;
const cropHeight =
this.canvasManager?.canvasHeight?.value ||
this.canvas?.canvasHeight ||
this.canvas.height;
canvasClipPath = new fabric.Rect({
left: this.canvas.width / 2,
top: this.canvas.height / 2,
width: cropWidth,
height: cropHeight,
originX: "center",
originY: "center",
fill: "#fff",
stroke: "transparent",
strokeWidth: 0,
});
canvasClipPath.set({
absolutePositioned: true,
});
canvasClipPath.setCoords();
}
// 普通模式使用画布尺寸
return await this._exportWithCanvasSize(
objectsToExport,
expPicType,
restoreOpacityInRedGreen,
canvasClipPath,
isCropByBg, // 是否使用背景大小裁剪
isEnhanceImg, // 是否是增强图片
);
}
/**
* 从图层收集对象(优化版本 - 通过ID查找画布中的真实对象
* @param {Object} layer 图层对象
* @param {Boolean} isChildren 是否递归收集子图层的对象
* @returns {Array} 画布中的真实对象数组
* @private
*/
_collectObjectsFromLayer(layer, isChildren = true) {
if (!layer) {
return [];
}
const realObjects = [];
// 收集当前图层的对象
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
for (const layerObj of layer.fabricObjects) {
if (!layerObj || !layerObj.id) continue;
// 通过ID在画布中查找真实对象
const realObj = this._findRealObjectById(layerObj.id);
if (realObj && realObj.visible !== false) {
realObjects.push(realObj);
}
}
}
if (layer.fabricObject) {
// 通过ID在画布中查找真实对象
const realObj = this._findRealObjectById(layer.fabricObject.id);
if (realObj && realObj.visible !== false) {
realObjects.push(realObj);
}
}
// 递归收集子图层的对象
if (isChildren && layer.children && layer.children.length > 0) {
for (let i = layer.children.length - 1; i >= 0; i--) {
const childLayer = layer.children[i];
const childObjects = this._collectObjectsFromLayer(childLayer, isChildren);
realObjects.push(...childObjects);
}
}
return realObjects;
}
/**
* 通过ID在画布中查找真实对象
* @param {String} objectId 对象ID
* @returns {Object|null} 画布中的真实对象
* @private
*/
_findRealObjectById(objectId) {
if (!objectId || !this.canvas) {
return null;
}
try {
// 使用helper工具查找对象
const result = findObjectById(this.canvas, objectId);
return result?.object || null;
} catch (error) {
console.warn(`查找对象 ${objectId} 失败:`, error);
return null;
}
}
/**
* 导出对象
* @param {Object} obj fabric对象
* @param {String} expPicType 导出类型
* @param {Boolean} isRedGreenMode 是否为红绿图模式
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
* @returns {String} 图片数据URL
* @private
*/
async _exportObject(
obj,
expPicType,
isRedGreenMode,
restoreOpacityInRedGreen
) {
// 红绿图模式下使用固定尺寸和裁剪
if (isRedGreenMode) {
return this._exportWithRedGreenMode(
[obj],
expPicType,
restoreOpacityInRedGreen
);
}
// 普通模式使用画布尺寸
return await this._exportWithCanvasSize(
[obj],
expPicType,
restoreOpacityInRedGreen
);
}
/**
* 按图层顺序收集对象(优化版本 - 从底到顶)
* @param {Array|null} layerIdArray 图层ID数组null表示所有图层
* @param {Boolean} isContainBg 是否包含背景图层
* @param {Boolean} isContainFixed 是否包含固定图层
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
* @param {Boolean} isContainNormalLayer 是否包含普通图层
* @param {Array} excludedLayers 排除的图层ID数组
* @returns {Array} 按正确顺序排列的真实对象数组
* @private
*/
_collectObjectsByLayerOrder(layerIdArray, isContainBg, isContainFixed, isContainFixedOther, isContainNormalLayer, excludedLayers) {
const objectsToExport = [];
const allLayers = this._getAllLayersFlattened(excludedLayers); // 获取扁平化的图层列表
// 图层数组是从顶到底的顺序,需要反向遍历以获得从底到顶的渲染顺序
for (let i = allLayers.length - 1; i >= 0; i--) {
const layer = allLayers[i];
// 如果指定了图层ID数组只处理指定的图层
if (layerIdArray && !layerIdArray.includes(layer.id)) continue;
// 检查图层类型过滤条件
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther, isContainNormalLayer))
continue;
if (layer.visible) {
const layerObjects = this._collectObjectsFromLayer(layer, false);
objectsToExport.push(...layerObjects);
}
}
return objectsToExport;
}
/**
* 获取扁平化的图层列表(包含子图层),排除指定的图层
* @param {Array} excludedLayers 排除的图层ID数组
* @returns {Array} 扁平化的图层数组
* @private
*/
_getAllLayersFlattened(excludedLayers) {
const flattenedLayers = [];
const rootLayers = this._getAllLayers();
const flattenLayer = (layer) => {
// 检查是否在排除列表中
if (excludedLayers && excludedLayers.includes(layer.id)) return;
flattenedLayers.push(layer);
// 递归处理子图层
if (layer.children && layer.children.length > 0) {
for (const childLayer of layer.children) {
flattenLayer(childLayer);
}
}
};
// 处理所有根图层
for (const layer of rootLayers) {
flattenLayer(layer);
}
return flattenedLayers;
}
/**
* 计算对象组的边界
* @param {Array} objects 对象数组
* @returns {Object} 边界信息 {left, top, width, height}
* @private
*/
_calculateGroupBounds(objects) {
if (!objects || objects.length === 0) {
return { left: 0, top: 0, width: 1, height: 1 };
}
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
objects.forEach((obj) => {
if (!obj || typeof obj.getBoundingRect !== "function") {
return;
}
const bounds = obj.getBoundingRect();
minX = Math.min(minX, bounds.left);
minY = Math.min(minY, bounds.top);
maxX = Math.max(maxX, bounds.left + bounds.width);
maxY = Math.max(maxY, bounds.top + bounds.height);
});
if (minX === Infinity || minY === Infinity) {
return { left: 0, top: 0, width: 1, height: 1 };
}
// 添加小量边距避免边缘裁切
const padding = 2;
return {
left: minX - padding,
top: minY - padding,
width: maxX - minX + padding * 2,
height: maxY - minY + padding * 2,
};
}
/**
* 克隆对象并添加到临时画布,调整位置偏移
* @param {fabric.Canvas} tempCanvas 临时画布
* @param {Object} obj 要克隆的对象
* @param {Object} bounds 边界信息
* @param {Boolean} isRedGreenMode 是否为红绿图模式
* @param {Boolean} restoreOpacityInRedGreen 是否恢复透明度
* @returns {Promise<Object>} 克隆的对象
* @private
*/
async _cloneAndAddObjectWithOffset(
tempCanvas,
obj,
bounds,
isRedGreenMode,
restoreOpacityInRedGreen
) {
try {
const cloned = await this._cloneObjectForExport(
obj,
isRedGreenMode && restoreOpacityInRedGreen
);
if (cloned) {
// 获取对象当前边界
const objBounds = obj.getBoundingRect();
// 计算相对于组边界的偏移
const offsetX = objBounds.left - bounds.left;
const offsetY = objBounds.top - bounds.top;
// 设置新位置(相对于临时画布的原点)
cloned.set({
left: offsetX + objBounds.width / 2,
top: offsetY + objBounds.height / 2,
originX: "center",
originY: "center",
});
cloned.setCoords();
tempCanvas.add(cloned);
return cloned;
}
} catch (error) {
console.warn(`克隆对象失败: ${obj?.id || "未知"}`, error);
}
return null;
}
/**
* 红绿图模式导出(使用固定图层底图作为画布尺寸和裁剪区域)
* @param {Array} objectsToExport 要导出的对象数组
* @param {String} expPicType 导出类型
* @param {Boolean} restoreOpacityInRedGreen 是否恢复透明度为1
* @returns {String} 图片数据URL
* @private
*/
async _exportWithRedGreenMode(
objectsToExport,
expPicType,
restoreOpacityInRedGreen
) {
// 获取固定图层对象(衣服底图)作为参考
const fixedLayerObject =
this._getFixedLayerObject() ?? this.canvas.clipPath;
if (!fixedLayerObject) {
console.warn("红绿图模式下未找到固定图层对象,使用画布尺寸");
return await this._exportWithCanvasSize(
objectsToExport,
expPicType,
restoreOpacityInRedGreen,
);
}
// 使用固定图层的实际显示尺寸作为导出画布尺寸
const canvasWidth = (fixedLayerObject.width);
const canvasHeight = (fixedLayerObject.height);
console.log(`红绿图模式导出,画布尺寸: ${canvasWidth}x${canvasHeight}`);
const tempFabricCanvas = new fabric.StaticCanvas()
tempFabricCanvas.setDimensions({
width: canvasWidth,
height: canvasHeight,
backgroundColor: null,
// enableRetinaScaling: true,
imageSmoothingEnabled: true,
});
// tempFabricCanvas.setZoom(1);
const ox = fixedLayerObject.left - fixedLayerObject.width * fixedLayerObject.scaleX / 2
const oy = fixedLayerObject.top - fixedLayerObject.height * fixedLayerObject.scaleY / 2
// console.log("==========", fixedLayerObject, ox, oy)
try {
// 克隆并添加所有对象到临时画布,需要调整位置相对于固定图层
for (let i = 0; i < objectsToExport.length; i++) {
const obj = objectsToExport[i];
const cloned = await this._cloneObjectForExport(
obj,
restoreOpacityInRedGreen && true
);
if (cloned) {
let scaleX = cloned.scaleX / fixedLayerObject.scaleX
let scaleY = cloned.scaleY / fixedLayerObject.scaleY
let top = (cloned.top - oy) * scaleY
let left = (cloned.left - ox) * scaleX
if (cloned.originX === "center" && cloned.originY === "center") {
top = canvasHeight / 2
left = canvasWidth / 2
}
cloned.set({
left: left,
top: top,
scaleX: scaleX,
scaleY: scaleY,
});
// 更新对象坐标
cloned.setCoords();
tempFabricCanvas.add(cloned);
}
}
// 渲染画布
tempFabricCanvas.renderAll();
// 生成图片
return this._generateHighQualityDataURL(tempFabricCanvas, expPicType);
} finally {
this._cleanupTempCanvas(tempFabricCanvas);
}
}
/**
* 普通模式导出(使用画布尺寸)
* @param {Array} objectsToExport 要导出的对象数组
* @param {String} expPicType 导出类型
* @param {Boolean} restoreOpacityInRedGreen 是否恢复透明度为1
* @param {Object} maskObject 裁剪对象
* @param {Boolean} isCropByBg 是否使用背景大小裁剪
* @param {Boolean} isEnhanceImg 是否是增强图片
* @returns {String} 图片数据URL
* @private
*/
async _exportWithCanvasSize(
objectsToExport,
expPicType,
restoreOpacityInRedGreen,
maskObject, // 裁剪对象
isCropByBg, // 是否使用背景大小裁剪
isEnhanceImg, // 是否是增强图片
) {
// 使用当前画布尺寸
// const canvasWidth =
// this.canvasManager?.canvasWidth?.value || this.canvas.width;
// const canvasHeight =
// this.canvasManager?.canvasHeight?.value || this.canvas.height;
// console.log(`普通模式导出,画布尺寸: ${canvasWidth}x${canvasHeight}`);
// 使用图层栅格化的方法导出图片
const dataURL = await createRasterizedImage({
canvas: this.canvas,
fabricObjects: objectsToExport,
format: expPicType, // 导出格式
isReturenDataURL: true, // 返回数据URL
maskObject: maskObject ?? null, // 使用裁剪对象
trimWhitespace: true, // 裁剪空白
trimPadding: 0, // 裁剪边距
restoreOpacityInRedGreen,
isCropByBg, // 是否使用背景大小裁剪
isEnhanceImg, // 是否是增强图片
});
// console.log("导出图片数据URL:", dataURL);
return dataURL;
// // 创建与画布相同尺寸的临时画布
// const scaleFactor = 2; // 高清导出
// const tempCanvas = document.createElement("canvas");
// tempCanvas.width = canvasWidth * scaleFactor;
// tempCanvas.height = canvasHeight * scaleFactor;
// tempCanvas.style.width = canvasWidth + "px";
// tempCanvas.style.height = canvasHeight + "px";
// const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, {
// width: canvasWidth,
// height: canvasHeight,
// backgroundColor: null,
// });
// tempFabricCanvas.enableRetinaScaling = true;
// tempFabricCanvas.imageSmoothingEnabled = true;
// tempFabricCanvas.setZoom(1);
// try {
// // 克隆并添加所有对象到临时画布
// for (const obj of objectsToExport) {
// const cloned = await this._cloneObjectForExport(
// obj,
// restoreOpacityInRedGreen && false, // 普通模式不强制恢复透明度
// );
// if (cloned) {
// tempFabricCanvas.add(cloned);
// }
// }
// // 渲染画布
// tempFabricCanvas.renderAll();
// // 生成图片
// return this._generateHighQualityDataURL(tempCanvas, expPicType);
// } finally {
// this._cleanupTempCanvas(tempFabricCanvas);
// }
}
/**
* 获取固定图层对象
* @returns {Object|null} 固定图层对象
* @private
*/
_getFixedLayerObject() {
const allLayers = this._getAllLayers();
const fixedLayer = allLayers.find((layer) => layer.isFixed);
if (!fixedLayer || !fixedLayer.fabricObject) {
return null;
}
// 如果有ID通过ID查找画布中的实际对象
if (fixedLayer.fabricObject.id) {
const result = findObjectById(this.canvas, fixedLayer.fabricObject.id);
return result.object || fixedLayer.fabricObject;
}
return fixedLayer.fabricObject;
}
/**
* 异步克隆fabric对象参照createRasterizedImage的方法
* @param {fabric.Object} obj 要克隆的对象
* @param {Array} propertiesToInclude 要包含的属性
* @returns {Promise<fabric.Object>} 克隆的对象
* @private
*/
_cloneObjectAsync(
obj,
propertiesToInclude = ["id", "layerId", "layerName", "name", "scaleX", "scaleY"]
) {
return new Promise((resolve, reject) => {
if (!obj) {
resolve(null);
return;
}
try {
obj.clone((cloned) => {
if (cloned) {
resolve(cloned);
} else {
reject(new Error("对象克隆失败"));
}
}, propertiesToInclude);
} catch (error) {
console.warn("克隆对象失败:", error);
resolve(null);
}
});
}
/**
* 克隆对象用于导出(优化版本)
* @param {Object} obj fabric对象
* @param {Boolean} forceRestoreOpacity 是否强制恢复透明度为1
* @param {Boolean} removeClipPath 是否移除裁剪路径
* @returns {Promise<Object>} 克隆的对象
* @private
*/
async _cloneObjectForExport(
obj,
forceRestoreOpacity = false,
removeClipPath = true
) {
if (!obj) return null;
try {
// 使用异步克隆方法
const cloned = await this._cloneObjectAsync(obj);
if (cloned) {
// 保持原始位置和属性
cloned.set({
selectable: false,
evented: false,
visible: true,
});
// 如果需要恢复透明度
if (forceRestoreOpacity) {
cloned.set({ opacity: 1 });
}
// 移除裁剪路径以避免绝对路径问题
if (removeClipPath && cloned.clipPath) {
console.log(`移除对象 ${cloned.id || "未知"} 的裁剪路径`);
cloned.clipPath = null;
}
return cloned;
}
} catch (error) {
console.warn("克隆对象失败:", error);
}
return null;
}
/**
* 导出对象组
* @param {Array} objectsToExport 要导出的对象数组
* @param {String} expPicType 导出类型
* @param {Boolean} isRedGreenMode 是否为红绿图模式
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
* @returns {Promise<String>} 图片数据URL
* @private
*/
async _exportObjectsAsGroup(
objectsToExport,
expPicType,
isRedGreenMode = false,
restoreOpacityInRedGreen = true
) {
if (!objectsToExport || objectsToExport.length === 0) {
throw new Error("没有可导出的对象");
}
// 计算所有对象的边界
const bounds = this._calculateGroupBounds(objectsToExport);
console.log("导出边界:", bounds);
// 创建高质量临时画布
const scaleFactor = 2; // 高清导出
const tempCanvas = document.createElement("canvas");
tempCanvas.width = bounds.width * scaleFactor;
tempCanvas.height = bounds.height * scaleFactor;
tempCanvas.style.width = bounds.width + "px";
tempCanvas.style.height = bounds.height + "px";
const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, {
width: bounds.width,
height: bounds.height,
backgroundColor: null, // 透明背景
});
// 启用高清缩放和图像平滑
tempFabricCanvas.enableRetinaScaling = true;
tempFabricCanvas.imageSmoothingEnabled = true;
tempFabricCanvas.setZoom(scaleFactor);
try {
// 克隆所有对象并添加到临时画布
const clonedObjects = [];
for (const obj of objectsToExport) {
const cloned = await this._cloneAndAddObjectWithOffset(
tempFabricCanvas,
obj,
bounds,
isRedGreenMode,
restoreOpacityInRedGreen
);
if (cloned) {
clonedObjects.push(cloned);
}
}
console.log(`成功克隆 ${clonedObjects.length} 个对象进行导出`);
// 渲染画布
tempFabricCanvas.renderAll();
// 生成高质量数据URL
return this._generateHighQualityDataURL(tempCanvas, expPicType);
} finally {
this._cleanupTempCanvas(tempFabricCanvas);
}
}
/**
* 获取裁剪路径对象(优化版本)
* @param {Object} fixedBounds 固定图层边界
* @returns {Promise<Object|null>} 裁剪路径对象
* @private
*/
async _getClipPathObject(fixedBounds) {
try {
// const allLayers = this._getAllLayers();
// // 查找第一个有裁剪遮罩的图层
// let clipObject = null;
// for (const layer of allLayers) {
// if (layer.clippingMask?.id) {
// const result = findObjectById(this.canvas, layer.clippingMask.id);
// if (result?.object) {
// clipObject = result.object;
// break;
// }
// }
// }
const clipObject = this.canvas?.clipPath;
if (!clipObject) {
console.warn("未找到可用的裁剪对象");
return null;
}
// 克隆对象作为裁剪路径
const clonedClipPath = await this._cloneObjectForExport(
clipObject,
false,
false
);
if (!clonedClipPath) {
console.warn("无法克隆裁剪对象");
return null;
}
// 调整裁剪路径的位置相对于固定图层
clonedClipPath.set({
left: clonedClipPath.left - fixedBounds.left,
top: clonedClipPath.top - fixedBounds.top,
absolutePositioned: true, // 使用绝对定位
});
// 更新坐标
clonedClipPath.setCoords();
console.log("成功创建裁剪路径:", {
objectType: clonedClipPath.type,
position: { left: clonedClipPath.left, top: clonedClipPath.top },
size: { width: clonedClipPath.width, height: clonedClipPath.height },
});
return clonedClipPath;
} catch (error) {
console.error("获取裁剪路径失败:", error);
return null;
}
}
/**
* 生成高质量数据URL
* @param {HTMLCanvasElement} canvas 画布元素
* @param {String} expPicType 导出类型
* @returns {String} 数据URL
* @private
*/
_generateHighQualityDataURL(canvas, expPicType) {
const format = expPicType.toLowerCase();
switch (format) {
case "jpg":
case "jpeg":
// 对于JPEG使用较高质量但JPEG不支持透明背景
return canvas.toDataURL("image/jpeg", 0.95);
case "svg":
// SVG导出需要特殊处理这里先返回高质量PNG
console.warn("SVG导出暂未实现返回高质量PNG格式");
return canvas.toDataURL("image/png", 1.0);
case "png":
default:
// PNG使用最高质量支持透明背景
return canvas.toDataURL("image/png", 1.0);
}
}
/**
* 生成空白图片
* @param {String} expPicType 导出类型
* @returns {String} 空白图片数据URL
* @private
*/
_generateEmptyImage(expPicType) {
const emptyCanvas = document.createElement("canvas");
emptyCanvas.width = 1;
emptyCanvas.height = 1;
// 确保透明背景
const ctx = emptyCanvas.getContext("2d");
ctx.clearRect(0, 0, 1, 1);
return this._generateHighQualityDataURL(emptyCanvas, expPicType);
}
/**
* 清理临时画布资源
* @param {fabric.StaticCanvas} tempFabricCanvas 临时Fabric画布
* @private
*/
_cleanupTempCanvas(tempFabricCanvas) {
if (tempFabricCanvas) {
try {
tempFabricCanvas.dispose();
} catch (error) {
console.warn("清理临时画布失败:", error);
}
}
}
/**
* 获取所有图层
* @returns {Array} 图层数组
* @private
*/
_getAllLayers() {
if (this.layerManager && this.layerManager.layers) {
return this.layerManager.layers.value || [];
}
return [];
}
/**
* 根据ID获取图层
* @param {String} layerId 图层ID
* @returns {Object|null} 图层对象
* @private
*/
_getLayerById(layerId) {
if (this.layerManager && this.layerManager.getLayerById) {
return this.layerManager.getLayerById(layerId);
}
// 备用方法:直接从图层数组中查找
const allLayers = this._getAllLayers();
return allLayers.find((layer) => layer.id === layerId) || null;
}
/**
* 检查图层是否应该包含在导出中
* @param {Object} layer 图层对象
* @param {Boolean} isContainBg 是否包含背景图层
* @param {Boolean} isContainFixed 是否包含固定图层
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
* @param {Boolean} isContainNormalLayer 是否包含普通图层
* @returns {Boolean} 是否应该包含
* @private
*/
_shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther, isContainNormalLayer) {
if (!layer) return false;
// 检查背景图层
if (layer.isBackground) {
return isContainBg;
}
// 检查固定图层
if (layer.isFixed) {
return isContainFixed;
}
// 检查其他固定图层
if (layer.isFixedOther) {
return isContainFixedOther;
}
// 印花图层始终导出
if (layer.isPrintTrims || layer.isPrintTrimsGroup) {
return true;
}
// 普通图层
return isContainNormalLayer;
}
}