Files
aida_front/src/component/Canvas/CanvasEditor/utils/imageHelper.js
2026-01-16 13:02:26 +08:00

2218 lines
62 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.
/* eslint-disable no-async-promise-executor */
import { fabric } from "fabric-with-all";
import { LayerType, OperationType, createBitmapLayer } from "./layerHelper";
// 导入新的复合命令
import { CreateImageLayerCommand } from "../commands/LayerCommands";
// 导入新的命令
import { ChangeFixedImageCommand, AddImageToLayerCommand } from "../commands/LayerCommands";
import { generateId } from "./helper";
import { isBoolean } from "lodash-es";
/**
* 加载并处理图片
* @param {string} imageSource - 图片URL或Base64字符串
* @param {Object} options - 配置选项
* @param {number} options.maxWidth - 最大宽度
* @param {number} options.maxHeight - 最大高度
* @param {boolean} options.centerOnCanvas - 是否居中图片
* @param {function} options.onLoad - 加载完成回调
* @returns {Promise<Object>} - 返回图片对象的Promise
*/
export function loadImage(imageSource, options = {}) {
return new Promise((resolve, reject) => {
fabric.Image.fromURL(
imageSource,
(fabricImage) => {
if (!fabricImage) {
reject(new Error("加载图片失败"));
return;
}
// 计算缩放比例
const imgWidth = fabricImage.width;
const imgHeight = fabricImage.height;
// 应用缩放
if (options.maxWidth && options.maxHeight) {
const scaleX = options.maxWidth / imgWidth;
const scaleY = options.maxHeight / imgHeight;
const scale = Math.min(scaleX, scaleY, 1); // 不超过原始大小
fabricImage.scale(scale);
}
// 设置图片位置 - 默认居中
if (options.centerOnCanvas !== false) {
fabricImage.set({
id: generateId("fabricImage"),
left: (options.canvasWidth || 800) / 2,
top: (options.canvasHeight || 600) / 2,
originX: "center",
originY: "center",
selectable: true,
hasControls: true,
hasBorders: true,
});
}
// 执行加载完成回调
if (typeof options.onLoad === "function") {
options.onLoad(fabricImage);
}
resolve(fabricImage);
},
{ crossOrigin: "anonymous" }
);
});
}
/**
* 创建图片图层
* @param {Object} layerManager - 图层管理器
* @param {Object} fabricImage - fabric图片对象
* @param {Object} toolManager - 工具管理器
* @param {string} layerName - 图层名称 (可选)
* @returns {Promise<string>} 新图层ID
*/
export async function createImageLayer({
layerManager,
fabricImage,
toolManager,
layerName = null,
undoable,
} = {}) {
if (!layerManager || !fabricImage) {
console.error("图层管理器或图片对象无效");
return null;
}
try {
// 使用新的复合命令
const createImageLayerCmd = new CreateImageLayerCommand({
layerManager,
fabricImage,
toolManager,
layerName,
});
// 设置命令的撤销状态
if (isBoolean(undoable)) createImageLayerCmd.undoable = undoable; // 是否撤销
// 执行复合命令
const newLayerId = await layerManager.commandManager.execute(createImageLayerCmd);
return newLayerId;
} catch (error) {
console.error("创建图片图层失败:", error);
throw error;
}
}
/**
* 更改固定图层的图像
* @param {Object} options - 配置选项
* @param {Object} options.layerManager - 图层管理器
* @param {string} options.fixedLayerId - 固定图层ID
* @param {Object} options.fabricImage - 新的图像对象
* @returns {Promise<boolean>} 是否成功更改
*/
export async function changeFixedImage({ layerManager, fixedLayerId, fabricImage } = {}) {
if (!layerManager || !fixedLayerId || !fabricImage) {
console.error("更改固定图层图像:参数无效");
return false;
}
try {
// 创建更改固定图层图像命令
const changeFixedImageCmd = new ChangeFixedImageCommand({
canvas: layerManager.canvas,
layers: layerManager.layers,
fixedLayerId,
newImage: fabricImage,
layerManager,
});
// 通过命令管理器执行
const result = await layerManager.commandManager.execute(changeFixedImageCmd);
if (result) {
console.log(`✅ 成功更改固定图层 "${fixedLayerId}" 的图像`);
}
return result;
} catch (error) {
console.error("更改固定图层图像失败:", error);
throw error;
}
}
/**
* 添加图片到指定图层或创建新图层
* @param {Object} options - 配置选项
* @param {Object} options.layerManager - 图层管理器
* @param {Object} options.toolManager - 工具管理器
* @param {Object} options.fabricImage - 图像对象
* @param {string} options.targetLayerId - 目标图层ID可选未指定则创建新图层
* @param {string} options.layerName - 图层名称(用于新建图层)
* @returns {Promise<string>} 图层ID
*/
export async function addImageToLayer({
layerManager,
toolManager,
fabricImage,
targetLayerId = null,
layerName = null,
} = {}) {
if (!layerManager || !fabricImage) {
console.error("添加图片到图层:参数无效");
return null;
}
try {
// 创建添加图片到图层命令
const addImageToLayerCmd = new AddImageToLayerCommand({
canvas: layerManager.canvas,
layers: layerManager.layers,
layerManager,
toolManager,
fabricImage,
targetLayerId,
layerName,
activeLayerId: layerManager.activeLayerId,
});
// 通过命令管理器执行
const resultLayerId = await layerManager.commandManager.execute(addImageToLayerCmd);
if (resultLayerId) {
if (targetLayerId) {
console.log(`✅ 成功添加图片到现有图层 "${targetLayerId}"`);
} else {
console.log(`✅ 成功创建新图层 "${resultLayerId}" 并添加图片`);
}
}
return resultLayerId;
} catch (error) {
console.error("添加图片到图层失败:", error);
throw error;
}
}
/**
* 从base64或者url加载图片并创建图层
* @param {str} imageUrl - base64字符串或图片URL
* @param {Object} layerManager - 图层管理器
* @param {Object} canvas - fabric.js画布实例
* @param {Object} options - 配置选项
* @returns {Promise<string>} 新图层ID的Promise
*/
export function loadImageUrlToLayer({ imageUrl, layerManager, canvas, toolManager }, options = {}) {
return new Promise(async (resolve, reject) => {
if (!imageUrl || !layerManager || !canvas) {
reject(new Error("参数无效"));
return;
}
try {
// 查找背景图层以获取尺寸
const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
// 设置最大宽高为背景图层的尺寸
const maxWidth = bgLayer?.canvasWidth || canvas.width;
const maxHeight = bgLayer?.canvasHeight || canvas.height;
// 加载并处理图片
const fabricImage = await loadImage(imageUrl, {
maxWidth: maxWidth * 0.8, // 默认图片最大宽度为背景宽度的80%
maxHeight: maxHeight * 0.8, // 默认图片最大高度为背景高度的80%
canvasWidth: canvas.width,
canvasHeight: canvas.height,
...options,
});
if (options.imageMode) {
imageModeHandler({
imageMode: options.imageMode,
newImage: fabricImage,
canvasWidth: maxWidth,
canvasHeight: maxHeight,
});
// 默认居中
fabricImage.set({
originX: "center",
originY: "center",
left: canvas.width / 2,
top: canvas.height / 2,
});
}
// 创建图片图层
const layerId = await createImageLayer({
layerManager,
fabricImage,
toolManager,
...options,
});
resolve(layerId);
} catch (error) {
console.error("处理图片失败:", error);
reject(error);
}
});
}
/**
* 从File对象加载图片并创建图层
* @param {File} file - 文件对象
* @param {Object} layerManager - 图层管理器
* @param {Object} canvas - fabric.js画布实例
* @param {Object} options - 配置选项
* @returns {Promise<string>} 新图层ID的Promise
*/
export function uploadImageAndCreateLayer(
{ file, layerManager, canvas, toolManager },
options = {}
) {
return new Promise((resolve, reject) => {
if (!file || !layerManager || !canvas) {
reject(new Error("参数无效"));
return;
}
const reader = new FileReader();
reader.onload = async (e) => {
try {
// 查找背景图层以获取尺寸
const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
// 设置最大宽高为背景图层的尺寸
const maxWidth = bgLayer?.canvasWidth || canvas.width;
const maxHeight = bgLayer?.canvasHeight || canvas.height;
// 加载并处理图片
const fabricImage = await loadImage(e.target.result, {
maxWidth: maxWidth * 0.8, // 默认图片最大宽度为背景宽度的80%
maxHeight: maxHeight * 0.8, // 默认图片最大高度为背景高度的80%
canvasWidth: canvas.width,
canvasHeight: canvas.height,
...options,
});
// 创建图片图层
const layerId = await createImageLayer({
layerManager,
fabricImage,
toolManager,
layerName: file.name,
});
resolve(layerId);
} catch (error) {
console.error("处理图片失败:", error);
reject(error);
}
};
reader.onerror = (error) => {
console.error("读取文件失败:", error);
reject(error);
};
reader.readAsDataURL(file);
});
}
/**
* 安全加载图片
* 添加错误处理和重试机制
* @param {string} imageSource - 图片URL或Base64字符串
* @param {Object} options - 配置选项
* @returns {Promise<Object>} - 返回图片对象的Promise
*/
export function safeLoadImage(imageSource, options = {}) {
return new Promise((resolve, reject) => {
let retries = options.retries || 1;
const attemptLoad = (attempt = 0) => {
loadImage(imageSource, options)
.then(resolve)
.catch((error) => {
if (attempt < retries) {
console.warn(`图片加载失败,正在重试 (${attempt + 1}/${retries})...`);
setTimeout(() => attemptLoad(attempt + 1), 500);
} else {
reject(error);
}
});
};
attemptLoad();
});
}
/**
* 从URL加载图片并更改固定图层
* @param {Object} options - 配置选项
* @param {string} options.imageUrl - 图片URL
* @param {Object} options.layerManager - 图层管理器
* @param {string} options.fixedLayerId - 固定图层ID
* @param {Object} options.imageOptions - 图片加载选项
* @returns {Promise<boolean>} 是否成功
*/
export function loadImageAndChangeFixedLayer({
imageUrl,
layerManager,
fixedLayerId,
imageOptions = {},
}) {
return new Promise((resolve, reject) => {
if (!imageUrl || !layerManager || !fixedLayerId) {
reject(new Error("参数无效"));
return;
}
loadImage(imageUrl, imageOptions)
.then(async (fabricImage) => {
try {
const result = await changeFixedImage({
layerManager,
fixedLayerId,
fabricImage,
});
resolve(result);
} catch (error) {
console.error("更改固定图层失败:", error);
reject(error);
}
})
.catch((error) => {
console.error("加载图片失败:", error);
reject(error);
});
});
}
/**
* 从File对象更改固定图层图像
* @param {Object} options - 配置选项
* @param {File} options.file - 图像文件对象
* @param {Object} options.layerManager - 图层管理器
* @param {string} options.layerId - 固定图层ID
* @param {Object} options.imageOptions - 图片加载选项
* @returns {Promise<string>} 新图像对象ID的Promise
*/
export function uploadImageAndChangeFixedLayer({ file, layerManager, layerId, imageOptions = {} }) {
return new Promise((resolve, reject) => {
if (!file || !layerManager || !layerId) {
reject(new Error("参数无效需要文件、图层管理器和图层ID"));
return;
}
// 验证文件类型
if (!file.type.startsWith("image/")) {
reject(new Error("无效的文件类型:必须是图像文件"));
return;
}
const reader = new FileReader();
reader.onload = async (e) => {
try {
// 查找目标固定图层以获取尺寸信息
const targetLayer = layerManager.layers.value.find((layer) => layer.id === layerId);
if (!targetLayer) {
throw new Error(`找不到图层 ID: ${layerId}`);
}
// 验证是否为固定图层
if (!targetLayer.isFixed && !targetLayer.isBackground) {
throw new Error("只能更改固定图层或背景图层的图像");
}
// 查找背景图层以获取画布尺寸
const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
const maxWidth = bgLayer?.canvasWidth || layerManager.canvas.width;
const maxHeight = bgLayer?.canvasHeight || layerManager.canvas.height;
// 加载并处理图片
const fabricImage = await loadImage(e.target.result, {
maxWidth: maxWidth,
maxHeight: maxHeight,
canvasWidth: layerManager.canvas.width,
canvasHeight: layerManager.canvas.height,
centerOnCanvas: true,
...imageOptions,
});
// 创建更改固定图层图像命令
const changeFixedImageCmd = new ChangeFixedImageCommand({
canvas: layerManager.canvas,
layers: layerManager.layers,
layerId: layerId,
newImageFile: file,
layerManager: layerManager,
});
// 通过命令管理器执行
const newImageId = await layerManager.commandManager.execute(changeFixedImageCmd);
if (newImageId) {
console.log(`✅ 成功更改固定图层 "${targetLayer.name}" 的图像新图像ID: ${newImageId}`);
resolve(newImageId);
} else {
throw new Error("更改固定图层图像失败");
}
} catch (error) {
console.error("处理图片失败:", error);
reject(error);
}
};
reader.onerror = (error) => {
console.error("读取文件失败:", error);
reject(new Error("文件读取失败"));
};
reader.readAsDataURL(file);
});
}
/**
* 从File对象加载图片并添加到指定图层 (简化版)
* @param {Object} options - 配置选项
* @param {File} options.file - 文件对象
* @param {Object} options.layerManager - 图层管理器
* @param {Object} options.toolManager - 工具管理器
* @param {string} options.targetLayerId - 目标图层ID可选
* @param {Object} options.imageOptions - 图片加载选项
* @returns {Promise<Object>} 返回 { layerId, imageId, wasLayerCreated } 的Promise
*/
export function uploadImageAndAddToLayer({
file,
layerManager,
toolManager,
targetLayerId = null,
imageOptions = {},
}) {
return new Promise((resolve, reject) => {
if (!file || !layerManager) {
reject(new Error("参数无效:需要文件和图层管理器"));
return;
}
// 验证文件类型
if (!file.type.startsWith("image/")) {
reject(new Error("无效的文件类型:必须是图像文件"));
return;
}
// 创建添加图像到图层命令
const addImageToLayerCmd = new AddImageToLayerCommand({
canvas: layerManager.canvas,
layers: layerManager.layers,
activeLayerId: layerManager.activeLayerId,
imageFile: file,
targetLayerId: targetLayerId,
layerManager: layerManager,
toolManager: toolManager,
});
// 通过命令管理器执行
layerManager.commandManager
.execute(addImageToLayerCmd)
.then((result) => {
if (result) {
console.log(`✅ 成功添加图像到图层,结果:`, result);
resolve(result);
} else {
throw new Error("添加图像到图层失败");
}
})
.catch((error) => {
console.error("添加图像到图层失败:", error);
reject(error);
});
});
}
/**
* 从File对象加载图片并添加到指定图层 (简化版)
* @param {Object} options - 配置选项
* @param {File} options.file - 文件对象
* @param {Object} options.layerManager - 图层管理器
* @param {Object} options.toolManager - 工具管理器
* @param {string} options.targetLayerId - 目标图层ID可选
* @param {Object} options.imageOptions - 图片加载选项
* @returns {Promise<Object>} 返回 { layerId, imageId, wasLayerCreated } 的Promise
*/
export function uploadImageAndAddToLayerSimple({
file,
layerManager,
toolManager,
targetLayerId = null,
imageOptions = {},
}) {
return new Promise((resolve, reject) => {
if (!file || !layerManager) {
reject(new Error("参数无效:需要文件和图层管理器"));
return;
}
// 验证文件类型
if (!file.type.startsWith("image/")) {
reject(new Error("无效的文件类型:必须是图像文件"));
return;
}
// 创建添加图像到图层命令
const addImageToLayerCmd = new AddImageToLayerCommand({
canvas: layerManager.canvas,
layers: layerManager.layers,
activeLayerId: layerManager.activeLayerId,
imageFile: file,
targetLayerId: targetLayerId,
layerManager: layerManager,
toolManager: toolManager,
});
// 通过命令管理器执行
layerManager.commandManager
.execute(addImageToLayerCmd)
.then((result) => {
if (result) {
console.log(`✅ 成功添加图像到图层,结果:`, result);
resolve(result);
} else {
throw new Error("添加图像到图层失败");
}
})
.catch((error) => {
console.error("添加图像到图层失败:", error);
reject(error);
});
});
}
/**
* 批量上传图片并创建图层
* @param {Object} options - 配置选项
* @param {FileList|Array<File>} options.files - 文件列表
* @param {Object} options.layerManager - 图层管理器
* @param {Object} options.canvas - fabric.js画布实例
* @param {Object} options.toolManager - 工具管理器
* @param {Object} options.imageOptions - 图片加载选项
* @param {function} options.onProgress - 进度回调函数
* @returns {Promise<Array<string>>} 新图层ID数组的Promise
*/
export async function batchUploadImagesAndCreateLayers({
files,
layerManager,
canvas,
toolManager,
imageOptions = {},
onProgress = null,
}) {
if (!files || files.length === 0) {
throw new Error("没有提供文件");
}
if (!layerManager || !canvas) {
throw new Error("缺少必要的参数:图层管理器或画布");
}
const results = [];
const errors = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
try {
// 调用进度回调
if (typeof onProgress === "function") {
onProgress({
current: i + 1,
total: files.length,
fileName: file.name,
status: "processing",
});
}
// 验证文件类型
if (!file.type.startsWith("image/")) {
console.warn(`跳过非图像文件: ${file.name}`);
continue;
}
// 上传图片并创建图层
const layerId = await uploadImageAndCreateLayer(
{ file, layerManager, canvas, toolManager },
imageOptions
);
results.push({
fileName: file.name,
layerId: layerId,
success: true,
});
// 调用进度回调
if (typeof onProgress === "function") {
onProgress({
current: i + 1,
total: files.length,
fileName: file.name,
status: "success",
layerId: layerId,
});
}
console.log(`✅ 成功处理文件: ${file.name}, 图层ID: ${layerId}`);
} catch (error) {
console.error(`❌ 处理文件失败: ${file.name}`, error);
errors.push({
fileName: file.name,
error: error.message,
success: false,
});
// 调用进度回调
if (typeof onProgress === "function") {
onProgress({
current: i + 1,
total: files.length,
fileName: file.name,
status: "error",
error: error.message,
});
}
}
}
// 输出批量处理结果
console.log(`📊 批量处理完成:`);
console.log(` ✅ 成功: ${results.length} 个文件`);
console.log(` ❌ 失败: ${errors.length} 个文件`);
if (errors.length > 0) {
console.warn("失败的文件:", errors);
}
return {
results: results,
errors: errors,
successCount: results.length,
errorCount: errors.length,
total: files.length,
};
}
/**
* 高级图像管理工具
* 提供批量图像处理、缓存、预加载等高级功能
*/
export class AdvancedImageManager {
constructor(canvasManager) {
this.canvasManager = canvasManager;
this.canvas = canvasManager.canvas;
this.layerManager = canvasManager.layerManager;
// 图像缓存
this.imageCache = new Map();
this.preloadQueue = [];
this.maxCacheSize = 50; // 最大缓存数量
// 批量操作状态
this.batchOperations = [];
this.isBatchMode = false;
// 性能监控
this.performanceMetrics = {
imageLoads: 0,
cacheHits: 0,
totalLoadTime: 0,
averageLoadTime: 0,
};
}
/**
* 预加载图像列表
* @param {Array} imageUrls 要预加载的图像URL数组
* @param {Object} options 选项
*/
async preloadImages(imageUrls, options = {}) {
const {
concurrency = 3, // 并发数量
timeout = 10000,
onProgress = null,
onError = null,
} = options;
const loadPromises = [];
const results = [];
let completed = 0;
// 分批并发加载
for (let i = 0; i < imageUrls.length; i += concurrency) {
const batch = imageUrls.slice(i, i + concurrency);
const batchPromises = batch.map(async (url, index) => {
try {
const startTime = performance.now();
const image = await this.loadAndCacheImage(url, { timeout });
const loadTime = performance.now() - startTime;
// 更新性能指标
this.updatePerformanceMetrics(loadTime);
completed++;
onProgress?.({ completed, total: imageUrls.length, url });
return { success: true, url, image, loadTime };
} catch (error) {
completed++;
onError?.({ url, error, completed, total: imageUrls.length });
return { success: false, url, error: error.message };
}
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return {
results,
summary: {
total: imageUrls.length,
successful: results.filter((r) => r.success).length,
failed: results.filter((r) => !r.success).length,
cacheHitRate: this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads,
averageLoadTime: this.performanceMetrics.averageLoadTime,
},
};
}
/**
* 加载并缓存图像
* @param {String} url 图像URL
* @param {Object} options 选项
*/
async loadAndCacheImage(url, options = {}) {
// 检查缓存
if (this.imageCache.has(url)) {
this.performanceMetrics.cacheHits++;
return this.imageCache.get(url);
}
// 加载新图像
const image = await this.loadImage(url, options);
// 添加到缓存
this.addToCache(url, image);
return image;
}
/**
* 开始批量操作模式
*/
startBatch() {
this.isBatchMode = true;
this.batchOperations = [];
}
/**
* 批量更换多个固定图层的图像
* @param {Array} operations 操作数组 [{layerType, imageUrl, options}, ...]
*/
async batchChangeFixedImages(operations) {
const operationResults = [];
if (this.isBatchMode) {
// 如果在批量模式下,只收集操作
this.batchOperations.push(
...operations.map((op) => ({
type: "changeFixed",
...op,
}))
);
return { queued: operations.length };
}
// 立即执行模式
for (const operation of operations) {
try {
const result = await this.canvasManager.changeFixedImage(operation.imageUrl, {
targetLayerType: operation.layerType,
...operation.options,
});
operationResults.push({ success: true, ...result, operation });
} catch (error) {
operationResults.push({
success: false,
error: error.message,
operation,
});
}
}
return {
results: operationResults,
summary: this.getSummary(operationResults),
};
}
/**
* 批量向多个图层添加图像
* @param {Array} operations 操作数组 [{layerId, imageUrl, position, options}, ...]
*/
async batchAddImagesToLayers(operations) {
const operationResults = [];
if (this.isBatchMode) {
this.batchOperations.push(
...operations.map((op) => ({
type: "addToLayer",
...op,
}))
);
return { queued: operations.length };
}
// 并发执行以提高性能
const concurrentOperations = operations.map(async (operation) => {
try {
const result = await this.canvasManager.addImageToLayer(
operation.imageUrl,
operation.layerId,
{
position: operation.position,
...operation.options,
}
);
return { success: true, ...result, operation };
} catch (error) {
return {
success: false,
error: error.message,
operation,
};
}
});
const concurrentResults = await Promise.all(concurrentOperations);
return {
results: concurrentResults,
summary: this.getSummary(concurrentResults),
};
}
/**
* 执行批量操作
*/
async executeBatch() {
if (!this.isBatchMode || this.batchOperations.length === 0) {
return { message: "No batch operations to execute" };
}
const results = [];
const startTime = performance.now();
// 按类型分组操作以优化执行
const groupedOps = this.groupOperationsByType(this.batchOperations);
// 执行分组操作
for (const [type, ops] of Object.entries(groupedOps)) {
try {
let typeResults;
switch (type) {
case "changeFixed":
typeResults = await this.batchChangeFixedImages(ops);
break;
case "addToLayer":
typeResults = await this.batchAddImagesToLayers(ops);
break;
default:
console.warn(`Unknown operation type: ${type}`);
continue;
}
if (typeResults.results) {
results.push(...typeResults.results);
}
} catch (error) {
console.error(`Batch execution failed for type ${type}:`, error);
// 继续执行其他类型的操作
}
}
const executionTime = performance.now() - startTime;
// 清理批量状态
this.isBatchMode = false;
this.batchOperations = [];
return {
results,
summary: {
...this.getSummary(results),
executionTime,
operationsPerSecond: results.length / (executionTime / 1000),
},
};
}
/**
* 创建图像替换模板
* @param {String} templateName 模板名称
* @param {Array} operations 操作定义
*/
createTemplate(templateName, operations) {
if (!this.templates) {
this.templates = new Map();
}
this.templates.set(templateName, {
name: templateName,
operations,
createdAt: new Date(),
usageCount: 0,
});
}
/**
* 应用模板
* @param {String} templateName 模板名称
* @param {Object} variables 变量替换映射
*/
async applyTemplate(templateName, variables = {}) {
const template = this.templates?.get(templateName);
if (!template) {
throw new Error(`Template "${templateName}" not found`);
}
// 替换模板中的变量
const operations = this.replaceTemplateVariables(template.operations, variables);
// 执行操作
const result = await this.batchAddImagesToLayers(operations);
// 更新使用计数
template.usageCount++;
return result;
}
/**
* 智能图像优化
* @param {String} imageUrl 图像URL
* @param {Object} targetSpecs 目标规格 {width, height, quality}
*/
async optimizeImage(imageUrl, targetSpecs) {
const image = await this.loadAndCacheImage(imageUrl);
// 检查是否需要优化
const currentSpecs = {
width: image.width,
height: image.height,
};
if (this.shouldOptimize(currentSpecs, targetSpecs)) {
return this.performImageOptimization(image, targetSpecs);
}
return image;
}
/**
* 清理缓存
* @param {String} strategy 清理策略 'lru', 'size', 'all'
*/
clearCache(strategy = "lru") {
switch (strategy) {
case "all":
this.imageCache.clear();
break;
case "size":
if (this.imageCache.size > this.maxCacheSize) {
const excess = this.imageCache.size - this.maxCacheSize;
const keys = Array.from(this.imageCache.keys());
for (let i = 0; i < excess; i++) {
this.imageCache.delete(keys[i]);
}
}
break;
case "lru":
// 实现 LRU 清理逻辑
this.implementLRUCleanup();
break;
}
}
// === 私有方法 ===
loadImage(url, options = {}) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`Image load timeout: ${url}`));
}, options.timeout || 10000);
fabric.Image.fromURL(
url,
(img) => {
clearTimeout(timeout);
if (!img || !img.getElement()) {
reject(new Error("Invalid image"));
return;
}
resolve(img);
},
{ crossOrigin: "anonymous" }
);
});
}
addToCache(url, image) {
// 检查缓存大小限制
if (this.imageCache.size >= this.maxCacheSize) {
this.clearCache("size");
}
// 添加时间戳用于 LRU
const cacheEntry = {
image,
lastUsed: Date.now(),
usageCount: 1,
};
this.imageCache.set(url, cacheEntry);
}
updatePerformanceMetrics(loadTime) {
this.performanceMetrics.imageLoads++;
this.performanceMetrics.totalLoadTime += loadTime;
this.performanceMetrics.averageLoadTime =
this.performanceMetrics.totalLoadTime / this.performanceMetrics.imageLoads;
}
getSummary(results) {
return {
total: results.length,
successful: results.filter((r) => r.success).length,
failed: results.filter((r) => !r.success).length,
successRate: results.filter((r) => r.success).length / results.length,
};
}
groupOperationsByType(operations) {
return operations.reduce((groups, op) => {
const type = op.type;
if (!groups[type]) groups[type] = [];
groups[type].push(op);
return groups;
}, {});
}
replaceTemplateVariables(operations, variables) {
return operations.map((op) => {
const newOp = { ...op };
// 替换字符串中的变量 {{variable}}
Object.keys(newOp).forEach((key) => {
if (typeof newOp[key] === "string") {
newOp[key] = newOp[key].replace(/\{\{(\w+)\}\}/g, (match, varName) => {
return variables[varName] || match;
});
}
});
return newOp;
});
}
shouldOptimize(current, target) {
const sizeThreshold = 0.8; // 80% 的阈值
return (
current.width > target.width * (1 / sizeThreshold) ||
current.height > target.height * (1 / sizeThreshold)
);
}
performImageOptimization(image, targetSpecs) {
// 实现图像优化逻辑
// 这里可以集成图像压缩、尺寸调整等功能
return image; // 简化实现
}
implementLRUCleanup() {
if (this.imageCache.size <= this.maxCacheSize) return;
// 按最后使用时间排序,移除最久未使用的
const entries = Array.from(this.imageCache.entries()).sort(
(a, b) => a[1].lastUsed - b[1].lastUsed
);
const toRemove = entries.slice(0, this.imageCache.size - this.maxCacheSize);
toRemove.forEach(([key]) => this.imageCache.delete(key));
}
// 获取性能报告
getPerformanceReport() {
return {
...this.performanceMetrics,
cacheSize: this.imageCache.size,
maxCacheSize: this.maxCacheSize,
cacheUtilization: this.imageCache.size / this.maxCacheSize,
recommendations: this.generatePerformanceRecommendations(),
};
}
generatePerformanceRecommendations() {
const recommendations = [];
if (this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads < 0.3) {
recommendations.push("考虑增加缓存大小以提高缓存命中率");
}
if (this.performanceMetrics.averageLoadTime > 2000) {
recommendations.push("图像加载时间较长考虑图像优化或CDN");
}
return recommendations;
}
}
/**
* 图像工具集
* 提供常用的图像处理和图层操作功能
*/
export const ImageUtils = {
// 基础图像加载
loadImage,
safeLoadImage,
// 图层操作
createImageLayer,
changeFixedImage,
addImageToLayer,
// 文件上传处理
uploadImageAndCreateLayer,
uploadImageAndChangeFixedLayer,
uploadImageAndAddToLayer,
uploadImageAndAddToLayerSimple,
batchUploadImagesAndCreateLayers,
// URL图像处理
loadImageAndChangeFixedLayer,
/**
* 快速创建图像图层 (别名)
* @param {File} file - 图像文件
* @param {Object} layerManager - 图层管理器
* @param {Object} canvas - 画布实例
* @param {Object} toolManager - 工具管理器
* @returns {Promise<string>} 图层ID
*/
quickCreateImageLayer: (file, layerManager, canvas, toolManager) => {
return uploadImageAndCreateLayer({
file,
layerManager,
canvas,
toolManager,
});
},
/**
* 快速更改固定图层图像 (别名)
* @param {File} file - 图像文件
* @param {string} layerId - 图层ID
* @param {Object} layerManager - 图层管理器
* @returns {Promise<string>} 新图像ID
*/
quickChangeFixedImage: (file, layerId, layerManager) => {
return uploadImageAndChangeFixedLayer({ file, layerId, layerManager });
},
/**
* 快速添加图像到图层 (别名)
* @param {File} file - 图像文件
* @param {Object} layerManager - 图层管理器
* @param {Object} toolManager - 工具管理器
* @param {string} targetLayerId - 目标图层ID (可选)
* @returns {Promise<Object>} 执行结果
*/
quickAddImageToLayer: (file, layerManager, toolManager, targetLayerId = null) => {
return uploadImageAndAddToLayerSimple({
file,
layerManager,
toolManager,
targetLayerId,
});
},
};
/**
* 栅格化画布对象为图像
* 参考fabric.brushes.js中的convertToImg方法考虑画布变换参数
* @param {Object} options - 配置选项
* @param {fabric.Canvas} options.canvas - fabric画布实例
* @param {Array} options.objects - 要栅格化的对象数组
* @param {Object} options.bounds - 边界框 {left, top, width, height} (可选)
* @param {boolean} options.trimWhitespace - 是否裁剪空白区域默认true
* @param {number} options.trimPadding - 裁剪时保留的空白边距默认10像素
* @param {number} options.quality - 图像质量 0-1默认1
* @param {string} options.format - 图像格式 'png'|'jpeg',默认'png'
* @returns {Promise<fabric.Image>} 栅格化后的图像对象
*/
export function rasterizeCanvasObjects(options = {}) {
return new Promise((resolve, reject) => {
try {
const {
canvas,
objects = [],
bounds = null,
trimWhitespace = true,
trimPadding = 10,
quality = 1,
format = "png",
} = options;
if (!canvas || !Array.isArray(objects)) {
reject(new Error("无效的参数:需要画布实例和对象数组"));
return;
}
if (objects.length === 0) {
reject(new Error("没有对象可栅格化"));
return;
}
// 使用改进的栅格化方法
_rasterizeUsingCanvasCopy(canvas, objects, {
trimWhitespace,
trimPadding,
quality,
format,
})
.then(resolve)
.catch(reject);
} catch (error) {
console.error("栅格化对象失败:", error);
reject(error);
}
});
}
/**
* 使用画布复制方式进行栅格化参考convertToImg实现
* @param {fabric.Canvas} canvas - fabric画布实例
* @param {Array} objects - 要栅格化的对象数组
* @param {Object} options - 配置选项
* @returns {Promise<fabric.Image>} 栅格化后的图像对象
* @private
*/
function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
return new Promise((resolve, reject) => {
try {
const { trimWhitespace = true, trimPadding = 10, quality = 1, format = "png" } = options;
// 保存原始状态
const originalObjects = canvas.getObjects();
const originalViewportTransform = [...canvas.viewportTransform];
const originalZoom = canvas.getZoom();
// 临时隐藏其他对象,只显示要栅格化的对象
const objectsToHide = originalObjects.filter((obj) => !objects.includes(obj));
// 隐藏不需要的对象
objectsToHide.forEach((obj) => {
obj._originalVisible = obj.visible;
obj.set("visible", false);
});
// 确保要栅格化的对象可见
objects.forEach((obj) => {
obj._originalVisible = obj.visible;
obj.set("visible", true);
});
// 重新渲染画布以应用可见性变化
canvas.renderAll();
// 等待一帧确保渲染完成
requestAnimationFrame(() => {
try {
// 获取画布的像素比例
const pixelRatio = canvas.getRetinaScaling();
// 复制画布元素(这会保持所有变换状态)
const copiedCanvas = fabric.util.copyCanvasElement(canvas.lowerCanvasEl);
let finalCanvas = copiedCanvas;
let trimOffset = { x: 0, y: 0 };
// 裁剪空白区域如果需要支持padding
if (trimWhitespace) {
const trimResult = _trimCanvas(copiedCanvas, trimPadding);
if (trimResult) {
finalCanvas = trimResult.canvas;
trimOffset = { x: trimResult.offset.x, y: trimResult.offset.y };
}
}
// 创建fabric图像对象
const fabricImage = new fabric.Image(finalCanvas);
if (!fabricImage) {
throw new Error("创建fabric图像失败");
}
// 获取画布变换参数
const pointerX = canvas.viewportTransform[4];
const pointerY = canvas.viewportTransform[5];
const zoom = canvas.getZoom();
// 计算最终位置参考convertToImg的实现
const finalLeft = (trimOffset.x / pixelRatio - pointerX) / zoom;
const finalTop = (trimOffset.y / pixelRatio - pointerY) / zoom;
const finalScaleX = 1 / pixelRatio / zoom;
const finalScaleY = 1 / pixelRatio / zoom;
// 设置图像属性
fabricImage.set({
id: generateId("rasterized_image_"),
left: finalLeft,
top: finalTop,
scaleX: finalScaleX,
scaleY: finalScaleY,
selectable: true,
hasControls: true,
hasBorders: true,
custom: {
type: "rasterized",
originalObjects: objects.map((obj) => obj.id).filter(Boolean),
rasterizedAt: new Date().toISOString(),
trimPadding: trimPadding,
},
});
fabricImage.setCoords();
// 恢复对象的原始可见性
_restoreObjectVisibility(originalObjects);
// 重新渲染画布
canvas.renderAll();
resolve(fabricImage);
} catch (error) {
// 确保恢复对象状态
_restoreObjectVisibility(originalObjects);
canvas.renderAll();
reject(error);
}
});
} catch (error) {
reject(error);
}
});
}
/**
* 恢复对象的原始可见性状态
* @param {Array} objects - 对象数组
* @private
*/
function _restoreObjectVisibility(objects) {
objects.forEach((obj) => {
if (Object.prototype.hasOwnProperty.call(obj, "_originalVisible")) {
obj.set("visible", obj._originalVisible);
delete obj._originalVisible;
}
});
}
/**
* 备用栅格化方法使用toDataURL方式
* 当画布复制方法不可用时的备选方案
* @param {fabric.Canvas} canvas - fabric画布实例
* @param {Array} objects - 要栅格化的对象数组
* @param {Object} options - 配置选项
* @returns {Promise<fabric.Image>} 栅格化后的图像对象
* @private
*/
function _rasterizeUsingDataURL(canvas, objects, options = {}) {
return new Promise((resolve, reject) => {
try {
const { quality = 1, format = "png" } = options;
// 保存原始状态
const originalObjects = canvas.getObjects();
// 临时移除其他对象
const objectsToRemove = originalObjects.filter((obj) => !objects.includes(obj));
objectsToRemove.forEach((obj) => {
canvas.remove(obj);
});
// 重新渲染画布
canvas.renderAll();
// 获取画布数据URL
const dataUrl = canvas.toDataURL({
format: format,
quality: quality,
multiplier: canvas.getRetinaScaling(),
});
// 恢复原始对象
objectsToRemove.forEach((obj) => {
canvas.add(obj);
});
// 恢复原始渲染顺序
canvas._objects = [...originalObjects];
canvas.renderAll();
// 创建fabric图像
fabric.Image.fromURL(
dataUrl,
(fabricImage) => {
if (!fabricImage) {
reject(new Error("创建fabric图像失败"));
return;
}
fabricImage.set({
id: generateId("rasterized_image_"),
left: 0,
top: 0,
selectable: true,
hasControls: true,
hasBorders: true,
custom: {
type: "rasterized",
originalObjects: objects.map((obj) => obj.id).filter(Boolean),
rasterizedAt: new Date().toISOString(),
},
});
fabricImage.setCoords();
resolve(fabricImage);
},
{ crossOrigin: "anonymous" }
);
} catch (error) {
reject(error);
}
});
}
/**
* 栅格化画布对象为图像(兼容版本)
* 自动选择最适合的栅格化方法
* @param {Object} options - 配置选项
* @returns {Promise<fabric.Image>} 栅格化后的图像对象
*/
export function rasterizeCanvasObjectsCompat(options = {}) {
const { canvas, objects = [] } = options;
// 检测是否支持copyCanvasElement
if (fabric.util.copyCanvasElement && canvas.lowerCanvasEl) {
// 使用画布复制方法(推荐)
return rasterizeCanvasObjects(options);
} else {
// 使用备用方法
console.warn("使用备用栅格化方法toDataURL");
return _rasterizeUsingDataURL(canvas, objects, options);
}
}
/**
* 高级栅格化方法:支持更多选项和优化
* @param {Object} options - 配置选项
* @param {fabric.Canvas} options.canvas - fabric画布实例
* @param {Array} options.objects - 要栅格化的对象数组
* @param {Object} options.bounds - 边界框 {left, top, width, height} (可选)
* @param {boolean} options.trimWhitespace - 是否裁剪空白区域默认true
* @param {number} options.quality - 图像质量 0-1默认1
* @param {string} options.format - 图像格式 'png'|'jpeg',默认'png'
* @param {boolean} options.preserveObjectState - 是否保持对象状态默认true
* @param {number} options.multiplier - 输出倍数默认使用画布的retina缩放
* @param {boolean} options.useBackgroundColor - 是否使用画布背景色默认false
* @returns {Promise<fabric.Image>} 栅格化后的图像对象
*/
export function rasterizeCanvasObjectsAdvanced(options = {}) {
return new Promise(async (resolve, reject) => {
try {
const {
canvas,
objects = [],
bounds = null,
trimWhitespace = true,
quality = 1,
format = "png",
preserveObjectState = true,
multiplier = null,
useBackgroundColor = false,
} = options;
if (!canvas || !Array.isArray(objects)) {
reject(new Error("无效的参数:需要画布实例和对象数组"));
return;
}
if (objects.length === 0) {
reject(new Error("没有对象可栅格化"));
return;
}
// 检测画布状态
const hasTransform =
canvas.getZoom() !== 1 ||
canvas.viewportTransform[4] !== 0 ||
canvas.viewportTransform[5] !== 0;
let result;
if (hasTransform && fabric.util.copyCanvasElement) {
// 有变换时使用画布复制方法
console.log("🎯 检测到画布变换,使用画布复制方法");
result = await _rasterizeUsingCanvasCopy(canvas, objects, {
trimWhitespace,
quality,
format,
});
} else {
// 无变换时可以使用更灵活的方法
console.log("📐 画布无变换,使用标准栅格化方法");
result = await _rasterizeUsingDataURL(canvas, objects, {
quality,
format,
});
}
resolve(result);
} catch (error) {
console.error("高级栅格化失败:", error);
reject(error);
}
});
}
/**
* 计算多个对象的边界框
* @param {Array} objects - 对象数组
* @returns {Object} 边界框 {left, top, width, height}
* @private
*/
function _calculateObjectsBounds(objects) {
if (!objects || objects.length === 0) {
return null;
}
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 null;
}
return {
left: minX,
top: minY,
width: maxX - minX,
height: maxY - minY,
};
}
/**
* 裁剪画布空白区域(支持保留边距)
* 参考fabric.util.trimCanvas方法添加padding支持
* @param {HTMLCanvasElement} canvas - 要裁剪的画布
* @param {number} padding - 保留的边距像素默认0
* @returns {Object|null} 裁剪结果 {canvas: 新画布, offset: {x, y}}
* @private
*/
function _trimCanvas(canvas, padding = 0) {
try {
const ctx = canvas.getContext("2d");
const w = canvas.width;
const h = canvas.height;
const imageData = ctx.getImageData(0, 0, w, h);
const pixels = imageData.data;
let minX = w;
let minY = h;
let maxX = 0;
let maxY = 0;
let hasContent = false;
// 扫描像素找到有内容的区域
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const alpha = pixels[(y * w + x) * 4 + 3];
if (alpha > 0) {
hasContent = true;
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
}
}
if (!hasContent) {
return null;
}
// 应用padding确保不超出原始画布边界
const paddedMinX = Math.max(0, minX - padding);
const paddedMinY = Math.max(0, minY - padding);
const paddedMaxX = Math.min(w - 1, maxX + padding);
const paddedMaxY = Math.min(h - 1, maxY + padding);
const trimWidth = paddedMaxX - paddedMinX + 1;
const trimHeight = paddedMaxY - paddedMinY + 1;
// 创建裁剪后的画布
const trimmedCanvas = document.createElement("canvas");
const trimmedCtx = trimmedCanvas.getContext("2d");
trimmedCanvas.width = trimWidth;
trimmedCanvas.height = trimHeight;
// 复制裁剪区域包含padding
const trimmedImageData = ctx.getImageData(paddedMinX, paddedMinY, trimWidth, trimHeight);
trimmedCtx.putImageData(trimmedImageData, 0, 0);
return {
canvas: trimmedCanvas,
offset: { x: paddedMinX, y: paddedMinY },
};
} catch (error) {
console.error("裁剪画布失败:", error);
return null;
}
}
/**
* 栅格化图层对象(简化版接口)
* @param {Object} options - 配置选项
* @param {fabric.Canvas} options.canvas - fabric画布实例
* @param {Object} options.layer - 图层对象
* @param {boolean} options.includeChildren - 是否包含子图层默认true
* @returns {Promise<fabric.Image>} 栅格化后的图像对象
*/
export function rasterizeLayer(options = {}) {
const { canvas, layer, includeChildren = true } = options;
if (!canvas || !layer) {
return Promise.reject(new Error("缺少必要参数:画布或图层"));
}
// 收集图层的所有对象
const objects = [];
if (layer.fabricObjects && Array.isArray(layer.fabricObjects)) {
objects.push(...layer.fabricObjects.filter(Boolean));
}
// 如果包含子图层
if (includeChildren && layer.children && Array.isArray(layer.children)) {
const collectChildObjects = (childLayer) => {
if (childLayer.fabricObjects && Array.isArray(childLayer.fabricObjects)) {
objects.push(...childLayer.fabricObjects.filter(Boolean));
}
if (childLayer.children && Array.isArray(childLayer.children)) {
childLayer.children.forEach(collectChildObjects);
}
};
layer.children.forEach(collectChildObjects);
}
if (objects.length === 0) {
return Promise.reject(new Error("图层没有可栅格化的对象"));
}
// 调用通用栅格化方法
return rasterizeCanvasObjects({
canvas,
objects,
trimWhitespace: true,
quality: 1,
format: "png",
});
}
/**
* 批量栅格化多个图层
* @param {Object} options - 配置选项
* @param {fabric.Canvas} options.canvas - fabric画布实例
* @param {Array} options.layers - 图层数组
* @param {function} options.onProgress - 进度回调
* @returns {Promise<Array>} 栅格化结果数组
*/
export async function batchRasterizeLayers(options = {}) {
const { canvas, layers = [], onProgress = null } = options;
if (!canvas || !Array.isArray(layers)) {
throw new Error("缺少必要参数:画布或图层数组");
}
const results = [];
const total = layers.length;
for (let i = 0; i < layers.length; i++) {
const layer = layers[i];
try {
onProgress?.({ current: i + 1, total, layer, status: "processing" });
const rasterizedImage = await rasterizeLayer({
canvas,
layer,
includeChildren: true,
});
results.push({
success: true,
layer,
image: rasterizedImage,
layerId: layer.id,
});
onProgress?.({ current: i + 1, total, layer, status: "success" });
} catch (error) {
console.error(`栅格化图层失败: ${layer.name || layer.id}`, error);
results.push({
success: false,
layer,
error: error.message,
layerId: layer.id,
});
onProgress?.({
current: i + 1,
total,
layer,
status: "error",
error: error.message,
});
}
}
return results;
}
/**
* 智能栅格化:根据对象类型和画布状态自动选择最佳方法
* @param {Object} options - 配置选项
* @param {fabric.Canvas} options.canvas - fabric画布实例
* @param {Array} options.objects - 要栅格化的对象数组
* @param {Object} options.strategy - 策略配置
* @returns {Promise<fabric.Image>} 栅格化后的图像对象
*/
export function smartRasterize(options = {}) {
const { canvas, objects = [], strategy = {} } = options;
// 分析对象和画布状态
const analysis = _analyzeRasterizationContext(canvas, objects);
// 选择最佳策略
const selectedStrategy = _selectOptimalStrategy(analysis, strategy);
console.log(`🧠 智能栅格化策略: ${selectedStrategy.method}`, {
reason: selectedStrategy.reason,
analysis: analysis,
});
// 执行对应的栅格化方法
switch (selectedStrategy.method) {
case "canvasCopy":
return rasterizeCanvasObjects({
canvas,
objects,
...selectedStrategy.options,
});
case "dataURL":
return _rasterizeUsingDataURL(canvas, objects, selectedStrategy.options);
case "advanced":
return rasterizeCanvasObjectsAdvanced({
canvas,
objects,
...selectedStrategy.options,
});
default:
return rasterizeCanvasObjects({ canvas, objects, ...options });
}
}
/**
* 分析栅格化上下文
* @param {fabric.Canvas} canvas - 画布实例
* @param {Array} objects - 对象数组
* @returns {Object} 分析结果
* @private
*/
function _analyzeRasterizationContext(canvas, objects) {
const zoom = canvas.getZoom();
const viewportTransform = canvas.viewportTransform;
const hasTransform = zoom !== 1 || viewportTransform[4] !== 0 || viewportTransform[5] !== 0;
// 分析对象类型分布
const objectTypes = objects.reduce((acc, obj) => {
const type = obj.type || "unknown";
acc[type] = (acc[type] || 0) + 1;
return acc;
}, {});
// 估算复杂度
const complexity = _estimateRenderingComplexity(objects);
// 计算画布利用率
const canvasArea = canvas.width * canvas.height;
const objectsBounds = _calculateObjectsBounds(objects);
const objectsArea = objectsBounds ? objectsBounds.width * objectsBounds.height : 0;
const utilization = objectsArea / canvasArea;
return {
hasTransform,
zoom,
objectCount: objects.length,
objectTypes,
complexity,
utilization,
canvasSize: { width: canvas.width, height: canvas.height },
objectsBounds,
supportsCanvasCopy: !!(fabric.util.copyCanvasElement && canvas.lowerCanvasEl),
};
}
/**
* 选择最优策略
* @param {Object} analysis - 分析结果
* @param {Object} userStrategy - 用户指定策略
* @returns {Object} 选择的策略
* @private
*/
function _selectOptimalStrategy(analysis, userStrategy = {}) {
// 用户指定策略优先
if (userStrategy.force) {
return {
method: userStrategy.force,
reason: "用户强制指定",
options: userStrategy.options || {},
};
}
// 画布变换场景
if (analysis.hasTransform && analysis.supportsCanvasCopy) {
return {
method: "canvasCopy",
reason: "画布有变换,使用画布复制方法保持变换状态",
options: { trimWhitespace: true },
};
}
// 高复杂度场景
if (analysis.complexity > 0.8) {
return {
method: "advanced",
reason: "高复杂度渲染,使用高级栅格化方法",
options: {
preserveObjectState: true,
useBackgroundColor: analysis.utilization > 0.5,
},
};
}
// 大量对象场景
if (analysis.objectCount > 50) {
return {
method: "dataURL",
reason: "大量对象使用dataURL方法优化性能",
options: { quality: 0.9 },
};
}
// 低利用率场景(空白较多)
if (analysis.utilization < 0.1) {
return {
method: "canvasCopy",
reason: "空白区域较多,使用画布复制+裁剪优化",
options: { trimWhitespace: true },
};
}
// 默认策略
return {
method: "canvasCopy",
reason: "标准场景,使用画布复制方法",
options: { trimWhitespace: true },
};
}
/**
* 估算渲染复杂度
* @param {Array} objects - 对象数组
* @returns {number} 复杂度分数 0-1
* @private
*/
function _estimateRenderingComplexity(objects) {
let complexity = 0;
let totalWeight = 0;
objects.forEach((obj) => {
let objectComplexity = 0;
let weight = 1;
// 基于对象类型的复杂度
switch (obj.type) {
case "path":
objectComplexity = 0.8;
weight = 2;
break;
case "group":
objectComplexity = 0.7;
weight = obj.getObjects?.()?.length || 3;
break;
case "text":
case "i-text":
case "textbox":
objectComplexity = 0.6;
weight = (obj.text?.length || 10) / 50;
break;
case "image":
objectComplexity = 0.4;
break;
case "rect":
case "circle":
case "ellipse":
objectComplexity = 0.2;
break;
default:
objectComplexity = 0.3;
}
// 考虑变换复杂度
if (obj.angle && obj.angle !== 0) objectComplexity += 0.1;
if (obj.scaleX !== 1 || obj.scaleY !== 1) objectComplexity += 0.1;
if (obj.skewX || obj.skewY) objectComplexity += 0.2;
// 考虑样式复杂度
if (obj.shadow) objectComplexity += 0.2;
if (obj.stroke) objectComplexity += 0.1;
if (obj.strokeDashArray?.length) objectComplexity += 0.1;
complexity += objectComplexity * weight;
totalWeight += weight;
});
return totalWeight > 0 ? Math.min(complexity / totalWeight, 1) : 0;
}
/**
* 栅格化工具集合
* 提供不同场景下的栅格化方法选择
*/
export const RasterizeUtils = {
// 基础栅格化
rasterizeCanvasObjects,
rasterizeLayer,
batchRasterizeLayers,
// 智能栅格化
smartRasterize,
// 兼容性栅格化
rasterizeCanvasObjectsCompat,
rasterizeCanvasObjectsAdvanced,
// 策略栅格化
fastRasterize: (canvas, objects) => {
return _rasterizeUsingDataURL(canvas, objects, { quality: 0.8 });
},
highQualityRasterize: (canvas, objects) => {
return rasterizeCanvasObjectsAdvanced({
canvas,
objects,
quality: 1,
trimWhitespace: true,
preserveObjectState: true,
});
},
compactRasterize: (canvas, objects) => {
return rasterizeCanvasObjects({
canvas,
objects,
trimWhitespace: true,
format: "jpeg",
quality: 0.9,
});
},
// 分析工具
analyzeRasterizationContext: _analyzeRasterizationContext,
estimateComplexity: _estimateRenderingComplexity,
calculateObjectsBounds: _calculateObjectsBounds,
/**
* 获取推荐的栅格化方法
* @param {fabric.Canvas} canvas - 画布实例
* @param {Array} objects - 对象数组
* @returns {Object} 推荐结果
*/
getRecommendation: (canvas, objects) => {
const analysis = _analyzeRasterizationContext(canvas, objects);
const strategy = _selectOptimalStrategy(analysis);
return {
recommendedMethod: strategy.method,
reason: strategy.reason,
analysis: analysis,
alternatives: _getAlternativeMethods(analysis),
};
},
};
/**
* 获取备选方法
* @param {Object} analysis - 分析结果
* @returns {Array} 备选方法列表
* @private
*/
function _getAlternativeMethods(analysis) {
const alternatives = [];
if (analysis.supportsCanvasCopy) {
alternatives.push({
method: "canvasCopy",
pros: ["保持变换状态", "高质量输出", "自动裁剪"],
cons: ["可能较慢"],
suitable: "有画布变换或需要高质量输出",
});
}
alternatives.push({
method: "dataURL",
pros: ["性能较好", "兼容性强", "处理大量对象"],
cons: ["不保持变换", "可能有质量损失"],
suitable: "大量对象或性能优先",
});
alternatives.push({
method: "advanced",
pros: ["智能优化", "完整功能", "自适应策略"],
cons: ["复杂度较高"],
suitable: "复杂场景或需要最佳效果",
});
return alternatives;
}
/** * 图像模式处理函数
* 根据不同的图像模式调整图像大小和位置
* @param {Object} params - 参数对象
* @param {string} params.imageMode - 图像模式
* @param {fabric.Image} params.newImage - 新图像对象
* @param {number} params.canvasWidth - 画布宽度
* @param {number} params.canvasHeight - 画布高度
*/
export const imageModeHandler = ({ imageMode, newImage, canvasWidth, canvasHeight }) => {
switch (imageMode) {
case "stretch":
// 拉伸模式 - 填充整个画布
newImage.scaleToWidth(canvasWidth);
newImage.scaleToHeight(canvasHeight);
break;
case "tile":
// 平铺模式 - 保持原始大小
newImage.scaleX = 1;
newImage.scaleY = 1;
break;
case "stretchTile":
// 拉伸平铺模式 - 填充整个画布,但保持宽高比
newImage.scaleToWidth(canvasWidth);
newImage.scaleToHeight(canvasHeight);
break;
case "stretchTileCrop":
// 拉伸平铺并裁剪模式 - 填充整个画布,可能
// 会裁剪图像以适应画布
newImage.scaleToWidth(canvasWidth);
newImage.scaleToHeight(canvasHeight);
// 这里可以添加裁剪逻辑,如果需要的话
// 例如使用fabric.Image.clipPath来裁剪图像
break;
case "contains": {
// 图片缩放后要保证最长边能完全显示在画布内 // 既要考虑画布的宽高比,也要考虑图像的宽高比 // 包含模式 - 保证图像在画布内完整显示
const canvasAspect = canvasWidth / canvasHeight;
const imageAspect = newImage.width / newImage.height;
// 保证图像在画布内完整显示 - 既要考虑画布的宽高比,也要考虑图像的宽高比
// 图片缩放后要保证最长边能完全显示在画布内
if (imageAspect > canvasAspect) {
// 图像更宽
newImage.scaleToWidth(canvasWidth);
} else {
// 图像更高
newImage.scaleToHeight(canvasHeight);
}
break;
}
}
};
/**
* 调整图像大小
* @param {string} base64 - 原始base64字符串
* @param {number} width - 目标宽度
* @param {number} height - 目标高度
* @returns {Promise<string>} 处理后的base64字符串
*/
export const resizeImage = async (base64, width, height) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = base64;
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
resolve(canvas.toDataURL());
};
img.onerror = reject;
});
};