1239 lines
33 KiB
JavaScript
1239 lines
33 KiB
JavaScript
|
|
//import { fabric } from "fabric-with-all";
|
|||
|
|
import { LayerType, OperationType, createBitmapLayer } from "./layerHelper";
|
|||
|
|
// 导入新的复合命令
|
|||
|
|
import { CreateImageLayerCommand } from "../commands/LayerCommands";
|
|||
|
|
// 导入新的命令
|
|||
|
|
import {
|
|||
|
|
ChangeFixedImageCommand,
|
|||
|
|
AddImageToLayerCommand,
|
|||
|
|
} from "../commands/LayerCommands";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 加载并处理图片
|
|||
|
|
* @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({
|
|||
|
|
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,
|
|||
|
|
} = {}) {
|
|||
|
|
if (!layerManager || !fabricImage) {
|
|||
|
|
console.error("图层管理器或图片对象无效");
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 使用新的复合命令
|
|||
|
|
const createImageLayerCmd = new CreateImageLayerCommand({
|
|||
|
|
layerManager,
|
|||
|
|
fabricImage,
|
|||
|
|
toolManager,
|
|||
|
|
layerName,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 执行复合命令
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从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,
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
};
|