Files
aida_front/src/component/Canvas/CanvasEditor/utils/imageHelper.js

2257 lines
62 KiB
JavaScript
Raw Normal View History

2025-06-18 11:05:23 +08:00
import { fabric } from "fabric-with-all";
2025-06-09 10:25:54 +08:00
import { LayerType, OperationType, createBitmapLayer } from "./layerHelper";
// 导入新的复合命令
import { CreateImageLayerCommand } from "../commands/LayerCommands";
// 导入新的命令
import {
ChangeFixedImageCommand,
AddImageToLayerCommand,
} from "../commands/LayerCommands";
2025-06-18 11:05:23 +08:00
import { generateId } from "./helper";
2025-06-09 10:25:54 +08:00
/**
* 加载并处理图片
* @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({
2025-06-18 11:05:23 +08:00
id: generateId("fabricImage"),
2025-06-09 10:25:54 +08:00
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,
2025-06-09 10:25:54 +08:00
} = {}) {
if (!layerManager || !fabricImage) {
console.error("图层管理器或图片对象无效");
return null;
}
try {
// 使用新的复合命令
const createImageLayerCmd = new CreateImageLayerCommand({
layerManager,
fabricImage,
toolManager,
layerName,
});
// 设置命令的撤销状态
if (isBoolean(undoable)) createImageLayerCmd.undoable = undoable; // 是否撤销
2025-06-09 10:25:54 +08:00
// 执行复合命令
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;
}
}
2025-06-23 00:40:45 +08:00
/**
* 从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,
});
// 创建图片图层
const layerId = await createImageLayer({
layerManager,
fabricImage,
toolManager,
...options,
2025-06-23 00:40:45 +08:00
});
resolve(layerId);
} catch (error) {
console.error("处理图片失败:", error);
reject(error);
}
});
}
2025-06-09 10:25:54 +08:00
/**
* 从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,
});
},
};
2025-06-22 13:52:28 +08:00
/**
* 栅格化画布对象为图像
* 参考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 (obj.hasOwnProperty("_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;
}
};