Files
aida_front/src/component/Canvas/CanvasEditor/managers/liquify/EnhancedLiquifyManager.js
2025-09-24 16:26:40 +08:00

918 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 增强版液化管理器
* 整合WebGL和CPU实现智能选择最佳渲染方式
*/
import { LiquifyWebGLManager } from "./LiquifyWebGLManager";
import { LiquifyCPUManager } from "./LiquifyCPUManager";
import { findLayerRecursively, LayerType } from "../../utils/layerHelper";
import i18n from "@/lang/index.ts";
const {t} = i18n.global;
export class EnhancedLiquifyManager {
/**
* 创建增强版液化管理器
* @param {Object} options 配置选项
*/
constructor(options = {}) {
this.config = {
// 性能阈值图像超过此尺寸会尝试使用WebGL
webglSizeThreshold: options.webglSizeThreshold || 1000 * 1000, // 默认100万像素
// 是否强制使用CPU模式
forceCPU: options.forceCPU || false,
// 是否强制使用WebGL模式
forceWebGL: options.forceWebGL || false,
// 网格大小
gridSize: options.gridSize || 15,
// 最大变形强度
maxStrength: options.maxStrength || 100,
// 平滑迭代次数
smoothingIterations: options.smoothingIterations || 2,
// 网格弹性因子
relaxFactor: options.relaxFactor || 0.25,
// WebGL网格精度
meshResolution: options.meshResolution || 64,
};
// 性能监控
this.performance = {
lastOperationTime: 0,
renderTimes: [], // 最近的渲染时间记录
isPerformanceIssue: false, // 是否存在性能问题
operationCount: 0, // 操作计数
};
// 初始化标志
this.initialized = false;
// 当前参数
this.params = {
size: 50, // 工具尺寸
pressure: 0.5, // 压力大小 (0-1)
distortion: 0, // 失真程度 (0-1)
power: 0.5, // 动力/强度 (0-1)
};
// 液化工具模式
this.modes = {
PUSH: "push",
CLOCKWISE: "clockwise",
COUNTERCLOCKWISE: "counterclockwise",
PINCH: "pinch",
EXPAND: "expand",
CRYSTAL: "crystal",
EDGE: "edge",
RECONSTRUCT: "reconstruct",
};
// 当前模式
this.currentMode = this.modes.PUSH;
// 图像数据和目标对象
this.originalImageData = null;
this.currentImageData = null;
this.targetObject = null;
this.targetLayerId = null;
// 创建渲染器实例
this.webglRenderer = null;
this.cpuRenderer = null;
// 当前激活的渲染器
this.activeRenderer = null;
this.renderMode = "unknown"; // 'webgl', 'cpu', 'unknown'
// 画布和管理器引用
this.canvas = options.canvas || null;
this.layerManager = options.layerManager || null;
// 渲染器状态
this.isWebGLAvailable = LiquifyWebGLManager.isSupported();
}
/**
* 初始化液化管理器
* @param {Object} options 配置选项
* @returns {Boolean} 是否初始化成功
*/
initialize(options = {}) {
if (options.canvas) this.canvas = options.canvas;
if (options.layerManager) this.layerManager = options.layerManager;
if (!this.canvas || !this.layerManager) {
console.error("液化管理器初始化失败缺少canvas或layerManager");
return false;
}
// 记录初始化时间,用于性能监控
this.performance.lastInitTime = Date.now();
// 创建CPU渲染器 (始终创建作为备选)
this.cpuRenderer = new LiquifyCPUManager({
gridSize: this.config.gridSize,
maxStrength: this.config.maxStrength,
smoothingIterations: this.config.smoothingIterations,
relaxFactor: this.config.relaxFactor,
});
// 检查是否应创建WebGL渲染器
if (this.isWebGLAvailable && !this.config.forceCPU) {
this.webglRenderer = new LiquifyWebGLManager({
gridSize: this.config.gridSize,
maxStrength: this.config.maxStrength,
meshResolution: this.config.meshResolution,
});
}
this.initialized = true;
return true;
}
/**
* 为液化操作准备图像
* @param {Object|String} target 目标对象或图层ID
* @returns {Promise<Object>} 准备结果
*/
async prepareForLiquify(target) {
if (!this.initialized) {
throw new Error("液化管理器未初始化");
}
let targetObject, targetLayerId;
// 处理传入的是图层ID的情况
if (typeof target === "string") {
targetLayerId = target;
const { layer } = findLayerRecursively(
this.layerManager.layers?.value ?? this.layerManager.layers,
targetLayerId
);
// 检查图层是否存在和是否有对象
let hasObjects = false;
if (layer) {
if (layer.type === "background" && layer.fabricObject) {
hasObjects = true;
targetObject = layer.fabricObject;
} else if (layer.fabricObjects && layer.fabricObjects.length > 0) {
hasObjects = true;
targetObject = layer.fabricObjects[0];
}
}
if (!hasObjects) {
throw new Error("目标图层为空或不存在");
}
} else if (typeof target === "object") {
// 传入的是对象
targetObject = target;
const { layer } = findLayerRecursively(
this.layerManager.layers?.value ?? this.layerManager.layers,
targetObject.layerId
);
if (layer) {
targetLayerId = layer.id;
} else {
throw new Error("无法找到目标对象所属图层");
}
} else {
throw new Error("无效的目标参数");
}
// 检查是否为图像对象
if (!targetObject || targetObject.type !== "image") {
throw new Error("目标对象不是图像,无法进行液化操作");
}
// 保存目标对象引用
this.targetObject = targetObject;
this.targetLayerId = targetLayerId;
// 获取图像数据
const imageData = await this._getImageData(targetObject);
if (!imageData) {
throw new Error("无法获取图像数据");
}
// 保存原始图像数据
this.originalImageData = imageData;
this.currentImageData = this._cloneImageData(imageData);
// 检查图像大小,选择适合的渲染器
await this._selectRenderer(imageData);
// 预热选定的渲染器
await this._warmupRenderer(imageData);
return {
targetObject: this.targetObject,
targetLayerId: this.targetLayerId,
imageData: this.currentImageData,
originalImageData: this.originalImageData,
renderMode: this.renderMode,
};
}
/**
* 根据图像大小和设备性能选择渲染器
* @param {ImageData} imageData 图像数据
* @private
*/
async _selectRenderer(imageData) {
// 计算图像大小
const pixelCount = imageData.width * imageData.height;
console.log(
`液化选择渲染器: 图像大小=${pixelCount}像素, WebGL可用=${this.isWebGLAvailable}`
);
// 默认使用CPU渲染器
this.activeRenderer = this.cpuRenderer;
this.renderMode = "cpu";
// 如果配置强制使用WebGL
if (this.config.forceWebGL && this.isWebGLAvailable && this.webglRenderer) {
console.log("液化功能: 强制使用WebGL渲染模式");
this.activeRenderer = this.webglRenderer;
this.renderMode = "webgl";
return;
}
// 如果配置强制使用CPU
if (this.config.forceCPU) {
console.log("液化功能: 强制使用CPU渲染模式");
return;
}
// 根据图像大小和WebGL可用性决定
if (
pixelCount > this.config.webglSizeThreshold / 2 && // 降低阈值让更多尺寸的图像使用WebGL
this.isWebGLAvailable &&
this.webglRenderer
) {
// 切换到WebGL渲染器
console.log("液化功能: 自动选择WebGL渲染模式(基于图像尺寸)");
this.activeRenderer = this.webglRenderer;
this.renderMode = "webgl";
} else {
console.log(
`液化功能: 使用CPU渲染模式${
!this.isWebGLAvailable ? " (WebGL不可用)" : ""
}`
);
}
}
/**
* 预热渲染器
* @param {ImageData} imageData 图像数据
* @private
*/
async _warmupRenderer(imageData) {
// 创建图像元素
const img = document.createElement("img");
// 将ImageData转换为URL
const canvas = document.createElement("canvas");
canvas.width = imageData.width;
canvas.height = imageData.height;
const ctx = canvas.getContext("2d");
ctx.putImageData(imageData, 0, 0);
// 使用Promise等待图像加载
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
img.src = canvas.toDataURL();
});
// 初始化当前渲染器
if (this.activeRenderer) {
if (this.renderMode === "webgl") {
this.activeRenderer.initialize(img);
} else {
this.activeRenderer.initialize(imageData);
}
}
}
/**
* 设置液化模式
* @param {String} mode 模式名称
*/
setMode(mode) {
if (Object.values(this.modes).includes(mode)) {
this.currentMode = mode;
// 同步更新当前渲染器
if (this.activeRenderer) {
this.activeRenderer.setMode(mode);
}
return true;
}
return false;
}
/**
* 设置液化参数
* @param {String} param 参数名称
* @param {Number} value 参数值
*/
setParam(param, value) {
if (param in this.params) {
this.params[param] = value;
// 同步更新当前渲染器 - 关键修复:确保参数正确传递
if (
this.activeRenderer &&
typeof this.activeRenderer.setParam === "function"
) {
console.log(`EnhancedLiquifyManager 设置参数: ${param}=${value}`);
this.activeRenderer.setParam(param, value);
} else {
console.warn(
`EnhancedLiquifyManager: 无法设置参数 ${param},渲染器未就绪`
);
}
return true;
}
console.warn(`EnhancedLiquifyManager: 无效参数 ${param}`);
return false;
}
/**
* 批量设置参数
* @param {Object} params 参数对象
*/
setParams(params) {
console.log("EnhancedLiquifyManager 批量设置参数:", params);
if (params && typeof params === "object") {
Object.entries(params).forEach(([key, value]) => {
this.setParam(key, value);
});
}
}
/**
* 获取当前参数
* @returns {Object} 当前参数对象
*/
getParams() {
return { ...this.params };
}
/**
* 重置参数为默认值
*/
resetParams() {
this.params = {
size: 50,
pressure: 0.5,
distortion: 0,
power: 0.5,
};
// 同步更新当前渲染器
if (this.activeRenderer) {
this.activeRenderer.resetParams();
}
}
/**
* 开始液化操作(记录初始点)
* @param {Number} x 初始X坐标
* @param {Number} y 初始Y坐标
*/
startLiquifyOperation(x, y) {
if (
this.activeRenderer &&
typeof this.activeRenderer.startDeformation === "function"
) {
this.activeRenderer.startDeformation(x, y);
}
console.log(
`开始液化操作,渲染模式=${this.renderMode}, 初始点: (${x}, ${y})`
);
}
/**
* 结束液化操作
*/
endLiquifyOperation() {
if (
this.activeRenderer &&
typeof this.activeRenderer.endDeformation === "function"
) {
this.activeRenderer.endDeformation();
}
console.log(`结束液化操作,渲染模式=${this.renderMode}`);
}
/**
* 应用液化变形
* @param {Object} target 目标对象
* @param {String} mode 液化模式
* @param {Object} params 液化参数
* @param {Number} x 操作中心点X坐标 (图像像素坐标)
* @param {Number} y 操作中心点Y坐标 (图像像素坐标)
* @returns {Promise<ImageData>} 处理后的图像数据
*/
async applyLiquify(target, mode, params, x, y) {
// 性能追踪开始
const startTime = performance.now();
// 如果首次调用,先准备环境
if (!this.targetObject || this.targetObject !== target) {
await this.prepareForLiquify(target);
}
// 更新模式和参数
if (mode) this.setMode(mode);
if (params) {
for (const [key, value] of Object.entries(params)) {
this.setParam(key, value);
}
}
// 验证坐标是否在图像范围内
if (!this.originalImageData) {
console.error("缺少原始图像数据");
return null;
}
const imageWidth = this.originalImageData.width;
const imageHeight = this.originalImageData.height;
// 坐标边界检查
if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight) {
console.warn(
`液化坐标超出图像范围: (${x}, ${y}), 图像尺寸: ${imageWidth}x${imageHeight}`
);
return null;
}
console.log(
`应用液化变形: 模式=${mode}, 图像坐标=(${x}, ${y}), 图像尺寸=${imageWidth}x${imageHeight}`
);
// 检查并应用变形
if (this.activeRenderer && typeof x === "number" && typeof y === "number") {
// 应用变形
let result;
if (this.renderMode === "webgl") {
// WebGL渲染器传入图像像素坐标
result = this.activeRenderer.applyDeformation(x, y);
} else {
// CPU渲染器传入图像像素坐标
result = this.activeRenderer.applyDeformation(x, y);
}
// 更新当前图像数据
if (result) {
this.currentImageData = result;
}
// 性能追踪结束
const endTime = performance.now();
this._trackPerformance(endTime - startTime);
return result;
}
console.error("无法应用液化变形:渲染器未初始化或坐标无效");
return null;
}
/**
* 追踪性能数据
* @param {Number} time 操作耗时(毫秒)
* @private
*/
_trackPerformance(time) {
this.performance.lastOperationTime = time;
this.performance.operationCount++;
// 维护最近10次操作的耗时记录
this.performance.renderTimes.push(time);
if (this.performance.renderTimes.length > 10) {
this.performance.renderTimes.shift();
}
// 计算平均耗时
const avgTime =
this.performance.renderTimes.reduce((sum, t) => sum + t, 0) /
this.performance.renderTimes.length;
// 检测性能问题
this.performance.isPerformanceIssue = avgTime > 100; // 如果平均耗时超过100毫秒
// 输出性能信息(调试用)
if (this.performance.operationCount % 10 === 0) {
console.log(
`液化性能数据: 模式=${this.renderMode}, 平均耗时=${avgTime.toFixed(
2
)}ms, 图像尺寸=${this.originalImageData?.width}x${
this.originalImageData?.height
}`
);
}
// 如果使用WebGL但性能差可以考虑切换到优化的CPU实现
if (
this.renderMode === "webgl" &&
this.performance.isPerformanceIssue &&
this.performance.operationCount > 5
) {
console.warn("WebGL液化性能不佳考虑切换到CPU模式");
// 注意:这里不自动切换,因为可能会导致中途渲染结果不一致
}
}
setRealtimeUpdater(realtimeUpdater) {
this.realtimeUpdater = realtimeUpdater;
}
/**
* 重置液化操作
* @returns {ImageData} 重置后的图像数据
*/
reset() {
if (!this.activeRenderer) return null;
// 使用当前渲染器重置
const result = this.activeRenderer.reset();
// 更新当前图像数据
if (result) {
this.currentImageData = result;
}
return result;
}
/**
* 检查图层是否可以液化
* @param {String} layerId 图层ID
* @returns {Object} 检查结果
*/
checkLayerForLiquify(layerId) {
if (!this.layerManager) {
return {
valid: false,
message: "图层管理器未初始化",
needsRasterization: false,
isImage: false,
isEmpty: true,
isGroup: false,
};
}
// 获取图层
// const layer = this.layerManager.getLayerById(layerId);
const { layer } = findLayerRecursively(
this.layerManager.layers?.value ?? this.layerManager.layers,
layerId
);
if (!layer) {
return {
valid: false,
message: "图层不存在",
needsRasterization: false,
isImage: false,
isEmpty: true,
isGroup: false,
};
}
// 检查图层是否为空
let objectsToCheck = [];
if (layer.isBackground || layer.type === "background" || layer.isFixed) {
// 背景图层使用 fabricObject (单数)
if (layer.fabricObject) {
objectsToCheck = [layer.fabricObject];
}
} else {
// 普通图层使用 fabricObjects (复数)
objectsToCheck = layer.fabricObjects || [];
}
if (objectsToCheck.length === 0) {
return {
valid: false,
message: t('Canvas.layerEmptyNoLiquidation'),
needsRasterization: false,
isImage: false,
isEmpty: true,
isGroup: false,
};
}
// 检查是否为单一图像
const singleObject = objectsToCheck.length === 1;
const isImage =
singleObject &&
(objectsToCheck[0].type === "image" ||
objectsToCheck[0].type === "rasterized-layer");
// 检查是否为组
const isGroup =
objectsToCheck.some((obj) => obj.type === "group") ||
layer.type === LayerType.GROUP ||
layer.children?.length > 0;
// 如果不是单一图像,需要栅格化
const needsRasterization = !isImage || isGroup;
return {
valid: isImage && !isGroup,
message: isImage ? "图层可以进行液化操作" : "需要先将图层栅格化",
needsRasterization: needsRasterization,
isImage: isImage,
isEmpty: false,
isGroup: isGroup,
};
}
/**
* 获取图像数据
* @param {Object} fabricObject Fabric图像对象
* @returns {Promise<ImageData>} 图像数据
* @private
*/
async _getImageData(fabricObject) {
return new Promise((resolve, reject) => {
try {
console.log("开始获取图像数据,对象类型:", fabricObject.type);
console.log("对象属性:", {
width: fabricObject.width,
height: fabricObject.height,
hasElement: !!fabricObject._element,
hasSrc: !!fabricObject.getSrc,
});
// 检查基本属性
if (!fabricObject.width || !fabricObject.height) {
reject(new Error("图像对象缺少有效的宽度或高度"));
return;
}
// 创建临时canvas - 使用原始图像尺寸不考虑fabric对象的缩放
const tempCanvas = document.createElement("canvas");
tempCanvas.width = fabricObject.width;
tempCanvas.height = fabricObject.height;
const tempCtx = tempCanvas.getContext("2d");
console.log(
`创建临时Canvas尺寸: ${tempCanvas.width}x${tempCanvas.height}`
);
// 处理不同的图像源
if (fabricObject._element) {
console.log("使用 _element 绘制图像");
// 检查_element是否有效
if (
!fabricObject._element.complete &&
fabricObject._element.tagName === "IMG"
) {
console.log("图像未加载完成,等待加载...");
fabricObject._element.onload = () => {
try {
tempCtx.drawImage(
fabricObject._element,
0,
0,
fabricObject.width,
fabricObject.height
);
const imageData = tempCtx.getImageData(
0,
0,
tempCanvas.width,
tempCanvas.height
);
console.log("✅ 图像加载完成后获取数据成功");
resolve(imageData);
} catch (error) {
console.error("图像加载后绘制失败:", error);
reject(error);
}
};
fabricObject._element.onerror = () => {
reject(new Error("图像加载失败"));
};
return;
}
// 直接绘制已加载的图像
tempCtx.drawImage(
fabricObject._element,
0,
0,
fabricObject.width,
fabricObject.height
);
} else if (
fabricObject.getSrc &&
typeof fabricObject.getSrc === "function"
) {
console.log("使用 getSrc() 方法获取图像源");
// 通过URL创建图像
const img = new Image();
img.crossOrigin = "anonymous"; // 避免跨域问题
img.onload = () => {
try {
console.log(
`图像加载成功,原始尺寸: ${img.naturalWidth}x${img.naturalHeight}`
);
tempCtx.drawImage(
img,
0,
0,
fabricObject.width,
fabricObject.height
);
const imageData = tempCtx.getImageData(
0,
0,
tempCanvas.width,
tempCanvas.height
);
console.log("✅ 通过URL获取图像数据成功");
resolve(imageData);
} catch (error) {
console.error("绘制图像失败:", error);
reject(error);
}
};
img.onerror = (error) => {
console.error("图像加载失败:", error);
reject(new Error("无法加载图像URL: " + fabricObject.getSrc()));
};
const srcUrl = fabricObject.getSrc();
console.log("加载图像URL:", srcUrl);
img.src = srcUrl;
return;
} else if (fabricObject.src) {
console.log("使用 src 属性获取图像源");
// 通过src属性创建图像
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = () => {
try {
tempCtx.drawImage(
img,
0,
0,
fabricObject.width,
fabricObject.height
);
const imageData = tempCtx.getImageData(
0,
0,
tempCanvas.width,
tempCanvas.height
);
console.log("✅ 通过src属性获取图像数据成功");
resolve(imageData);
} catch (error) {
console.error("通过src绘制图像失败:", error);
reject(error);
}
};
img.onerror = (error) => {
console.error("通过src加载图像失败:", error);
reject(new Error("无法加载图像src: " + fabricObject.src));
};
console.log("加载图像src:", fabricObject.src);
img.src = fabricObject.src;
return;
} else {
console.error("无法找到有效的图像源");
reject(
new Error("图像对象缺少有效的图像源_element, getSrc, 或 src")
);
return;
}
// 如果走到这里说明使用了_element直接绘制
try {
const imageData = tempCtx.getImageData(
0,
0,
tempCanvas.width,
tempCanvas.height
);
console.log(
`✅ 获取图像数据成功: 对象尺寸=${fabricObject.width}x${fabricObject.height}, ` +
`对象缩放=(${fabricObject.scaleX}, ${fabricObject.scaleY}), ` +
`图像数据尺寸=${imageData.width}x${imageData.height}`
);
resolve(imageData);
} catch (error) {
console.error("获取ImageData失败:", error);
reject(new Error("无法从Canvas获取图像数据: " + error.message));
}
} catch (error) {
console.error("_getImageData 执行失败:", error);
reject(error);
}
});
}
/**
* 克隆图像数据
* @param {ImageData} imageData 原始图像数据
* @returns {ImageData} 克隆的图像数据
* @private
*/
_cloneImageData(imageData) {
if (!imageData) return null;
// 使用新的浏览器API直接复制
if (typeof ImageData.prototype.constructor === "function") {
try {
return new ImageData(
new Uint8ClampedArray(imageData.data),
imageData.width,
imageData.height
);
} catch (e) {
console.warn("使用备选方法克隆ImageData");
}
}
// 备选方法
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = imageData.width;
canvas.height = imageData.height;
ctx.putImageData(imageData, 0, 0);
return ctx.getImageData(0, 0, imageData.width, imageData.height);
}
/**
* 释放资源
*/
dispose() {
// 释放渲染器资源
if (this.webglRenderer) {
this.webglRenderer.dispose();
this.webglRenderer = null;
}
if (this.cpuRenderer) {
this.cpuRenderer.dispose();
this.cpuRenderer = null;
}
// 清除引用
this.activeRenderer = null;
this.canvas = null;
this.layerManager = null;
this.targetObject = null;
this.originalImageData = null;
this.currentImageData = null;
this.initialized = false;
this.renderMode = "unknown";
}
/**
* 获取当前状态信息
* @returns {Object} 状态信息
*/
getStatus() {
return {
initialized: this.initialized,
renderMode: this.renderMode,
isWebGLAvailable: this.isWebGLAvailable,
currentMode: this.currentMode,
params: { ...this.params },
performance: { ...this.performance },
imageSize: this.originalImageData
? `${this.originalImageData.width}x${this.originalImageData.height}`
: "N/A",
};
}
}