/* 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} - 返回图片对象的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} 新图层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} 是否成功更改 */ 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} 图层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} 新图层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} 新图层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} - 返回图片对象的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} 是否成功 */ 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} 新图像对象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} 返回 { 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} 返回 { 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} 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>} 新图层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} 图层ID */ quickCreateImageLayer: (file, layerManager, canvas, toolManager) => { return uploadImageAndCreateLayer({ file, layerManager, canvas, toolManager, }); }, /** * 快速更改固定图层图像 (别名) * @param {File} file - 图像文件 * @param {string} layerId - 图层ID * @param {Object} layerManager - 图层管理器 * @returns {Promise} 新图像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} 执行结果 */ 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} 栅格化后的图像对象 */ 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} 栅格化后的图像对象 * @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} 栅格化后的图像对象 * @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} 栅格化后的图像对象 */ 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} 栅格化后的图像对象 */ 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} 栅格化后的图像对象 */ 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} 栅格化结果数组 */ 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} 栅格化后的图像对象 */ 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} 处理后的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; }); };