355 lines
9.9 KiB
JavaScript
355 lines
9.9 KiB
JavaScript
/**
|
||
* 图片导出管理器
|
||
* 负责处理画布的图片导出功能,支持多种导出选项和图层过滤
|
||
*/
|
||
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 {String} options.layerId 导出具体图层ID
|
||
* @param {Array} options.layerIdArray 导出多个图层ID数组
|
||
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
|
||
* @returns {String} 导出的图片数据URL
|
||
*/
|
||
exportImage(options = {}) {
|
||
const {
|
||
isContainBg = false,
|
||
isContainFixed = false,
|
||
layerId = "",
|
||
layerIdArray = [],
|
||
expPicType = "png"
|
||
} = options;
|
||
|
||
try {
|
||
// 如果指定了具体图层ID,导出指定图层
|
||
if (layerId) {
|
||
return this._exportSpecificLayer(layerId, expPicType);
|
||
}
|
||
|
||
// 如果指定了多个图层ID,导出多个图层
|
||
if (layerIdArray && layerIdArray.length > 0) {
|
||
return this._exportMultipleLayers(layerIdArray, expPicType, isContainBg, isContainFixed);
|
||
}
|
||
|
||
// 默认导出所有可见图层
|
||
return this._exportAllLayers(expPicType, isContainBg, isContainFixed);
|
||
|
||
} catch (error) {
|
||
console.error("导出图片失败:", error);
|
||
throw new Error(`图片导出失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 导出指定单个图层
|
||
* @param {String} layerId 图层ID
|
||
* @param {String} expPicType 导出类型
|
||
* @returns {String} 图片数据URL
|
||
* @private
|
||
*/
|
||
_exportSpecificLayer(layerId, expPicType) {
|
||
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 tempCanvas = this._createExportCanvas();
|
||
const tempFabricCanvas = this._createTempFabricCanvas(tempCanvas);
|
||
|
||
try {
|
||
// 只添加指定图层的对象
|
||
this._addLayerObjectsToCanvas(tempFabricCanvas, layer);
|
||
|
||
// 渲染并导出
|
||
tempFabricCanvas.renderAll();
|
||
return this._generateDataURL(tempCanvas, expPicType);
|
||
|
||
} finally {
|
||
this._cleanupTempCanvas(tempFabricCanvas);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 导出多个指定图层
|
||
* @param {Array} layerIdArray 图层ID数组
|
||
* @param {String} expPicType 导出类型
|
||
* @param {Boolean} isContainBg 是否包含背景图层
|
||
* @param {Boolean} isContainFixed 是否包含固定图层
|
||
* @returns {String} 图片数据URL
|
||
* @private
|
||
*/
|
||
_exportMultipleLayers(layerIdArray, expPicType, isContainBg, isContainFixed) {
|
||
if (!this.layerManager) {
|
||
throw new Error("图层管理器未初始化");
|
||
}
|
||
|
||
// 创建临时画布
|
||
const tempCanvas = this._createExportCanvas();
|
||
const tempFabricCanvas = this._createTempFabricCanvas(tempCanvas);
|
||
|
||
try {
|
||
// 按照图层顺序添加指定的图层
|
||
const allLayers = this._getAllLayers();
|
||
|
||
allLayers.forEach(layer => {
|
||
if (!layerIdArray.includes(layer.id)) return;
|
||
|
||
// 检查图层类型过滤条件
|
||
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed)) return;
|
||
|
||
if (layer.visible) {
|
||
this._addLayerObjectsToCanvas(tempFabricCanvas, layer);
|
||
}
|
||
});
|
||
|
||
// 渲染并导出
|
||
tempFabricCanvas.renderAll();
|
||
return this._generateDataURL(tempCanvas, expPicType);
|
||
|
||
} finally {
|
||
this._cleanupTempCanvas(tempFabricCanvas);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 导出所有图层
|
||
* @param {String} expPicType 导出类型
|
||
* @param {Boolean} isContainBg 是否包含背景图层
|
||
* @param {Boolean} isContainFixed 是否包含固定图层
|
||
* @returns {String} 图片数据URL
|
||
* @private
|
||
*/
|
||
_exportAllLayers(expPicType, isContainBg, isContainFixed) {
|
||
// 创建临时画布
|
||
const tempCanvas = this._createExportCanvas();
|
||
const tempFabricCanvas = this._createTempFabricCanvas(tempCanvas);
|
||
|
||
try {
|
||
// 获取所有图层并按顺序添加
|
||
const allLayers = this._getAllLayers();
|
||
|
||
allLayers.forEach(layer => {
|
||
// 检查图层类型过滤条件
|
||
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed)) return;
|
||
|
||
if (layer.visible) {
|
||
this._addLayerObjectsToCanvas(tempFabricCanvas, layer);
|
||
}
|
||
});
|
||
|
||
// 渲染并导出
|
||
tempFabricCanvas.renderAll();
|
||
return this._generateDataURL(tempCanvas, expPicType);
|
||
|
||
} finally {
|
||
this._cleanupTempCanvas(tempFabricCanvas);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 判断是否应该包含该图层
|
||
* @param {Object} layer 图层对象
|
||
* @param {Boolean} isContainBg 是否包含背景图层
|
||
* @param {Boolean} isContainFixed 是否包含固定图层
|
||
* @returns {Boolean} 是否包含
|
||
* @private
|
||
*/
|
||
_shouldIncludeLayer(layer, isContainBg, isContainFixed) {
|
||
// 背景图层处理
|
||
if (layer.type === 'background' || layer.isBackground) {
|
||
return isContainBg;
|
||
}
|
||
|
||
// 固定图层处理
|
||
if (layer.type === 'fixed' || layer.isFixed || layer.locked) {
|
||
return isContainFixed;
|
||
}
|
||
|
||
// 其他图层默认包含
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 创建导出用的临时画布
|
||
* @returns {HTMLCanvasElement} 临时画布
|
||
* @private
|
||
*/
|
||
_createExportCanvas() {
|
||
const tempCanvas = document.createElement("canvas");
|
||
tempCanvas.width = this.canvas.width || 800;
|
||
tempCanvas.height = this.canvas.height || 600;
|
||
return tempCanvas;
|
||
}
|
||
|
||
/**
|
||
* 创建临时Fabric画布
|
||
* @param {HTMLCanvasElement} tempCanvas 临时画布元素
|
||
* @returns {fabric.StaticCanvas} 临时Fabric画布
|
||
* @private
|
||
*/
|
||
_createTempFabricCanvas(tempCanvas) {
|
||
const { fabric } = window;
|
||
|
||
const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, {
|
||
width: this.canvas.width || 800,
|
||
height: this.canvas.height || 600,
|
||
backgroundColor: this.canvas.backgroundColor || 'transparent'
|
||
});
|
||
|
||
// 设置高质量渲染选项
|
||
tempFabricCanvas.enableRetinaScaling = true;
|
||
tempFabricCanvas.imageSmoothingEnabled = true;
|
||
|
||
return tempFabricCanvas;
|
||
}
|
||
|
||
/**
|
||
* 将图层对象添加到临时画布
|
||
* @param {fabric.StaticCanvas} tempCanvas 临时画布
|
||
* @param {Object} layer 图层对象
|
||
* @private
|
||
*/
|
||
_addLayerObjectsToCanvas(tempCanvas, layer) {
|
||
if (!layer) return;
|
||
|
||
// 处理背景图层
|
||
if (layer.type === 'background' && layer.fabricObject) {
|
||
this._cloneAndAddObject(tempCanvas, layer.fabricObject);
|
||
return;
|
||
}
|
||
|
||
// 处理普通图层的对象
|
||
if (layer.fabricObjects && Array.isArray(layer.fabricObjects)) {
|
||
layer.fabricObjects.forEach(obj => {
|
||
if (obj && obj.visible !== false) {
|
||
this._cloneAndAddObject(tempCanvas, obj);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 处理单个fabricObject的情况
|
||
if (layer.fabricObject && !layer.fabricObjects) {
|
||
this._cloneAndAddObject(tempCanvas, layer.fabricObject);
|
||
}
|
||
|
||
// 处理分组图层的子图层
|
||
if (layer.children && Array.isArray(layer.children)) {
|
||
layer.children.forEach(childLayerId => {
|
||
const childLayer = this._getLayerById(childLayerId);
|
||
if (childLayer && childLayer.visible) {
|
||
this._addLayerObjectsToCanvas(tempCanvas, childLayer);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 克隆并添加对象到临时画布
|
||
* @param {fabric.StaticCanvas} tempCanvas 临时画布
|
||
* @param {fabric.Object} obj Fabric对象
|
||
* @private
|
||
*/
|
||
_cloneAndAddObject(tempCanvas, obj) {
|
||
if (!obj) return;
|
||
|
||
try {
|
||
obj.clone((cloned) => {
|
||
if (cloned) {
|
||
// 确保克隆对象的属性正确
|
||
cloned.set({
|
||
selectable: false,
|
||
evented: false,
|
||
visible: true
|
||
});
|
||
tempCanvas.add(cloned);
|
||
}
|
||
}, ['id', 'layerId', 'name']); // 保留自定义属性
|
||
} catch (error) {
|
||
console.warn("克隆对象失败:", error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 生成数据URL
|
||
* @param {HTMLCanvasElement} canvas 画布元素
|
||
* @param {String} expPicType 导出类型
|
||
* @returns {String} 数据URL
|
||
* @private
|
||
*/
|
||
_generateDataURL(canvas, expPicType) {
|
||
const format = expPicType.toLowerCase();
|
||
|
||
switch (format) {
|
||
case 'jpg':
|
||
case 'jpeg':
|
||
return canvas.toDataURL('image/jpeg', 0.9);
|
||
case 'svg':
|
||
// SVG导出需要特殊处理,这里先返回PNG
|
||
console.warn("SVG导出暂未实现,返回PNG格式");
|
||
return canvas.toDataURL('image/png', 1.0);
|
||
case 'png':
|
||
default:
|
||
return canvas.toDataURL('image/png', 1.0);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 清理临时画布资源
|
||
* @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;
|
||
}
|
||
} |