合并画布代码

This commit is contained in:
X1627315083
2025-06-18 11:05:23 +08:00
parent 903c0ebdf5
commit 9c7fae36eb
118 changed files with 23633 additions and 8201 deletions

View File

@@ -4,6 +4,7 @@
*/
import { LiquifyWebGLManager } from "./LiquifyWebGLManager";
import { LiquifyCPUManager } from "./LiquifyCPUManager";
import { LayerType } from "../../utils/layerHelper";
export class EnhancedLiquifyManager {
/**
@@ -313,16 +314,40 @@ export class EnhancedLiquifyManager {
if (param in this.params) {
this.params[param] = value;
// 同步更新当前渲染器
if (this.activeRenderer) {
// 同步更新当前渲染器 - 关键修复:确保参数正确传递
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} 当前参数对象
@@ -348,6 +373,36 @@ export class EnhancedLiquifyManager {
}
}
/**
* 开始液化操作(记录初始点)
* @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 目标对象
@@ -468,7 +523,9 @@ export class EnhancedLiquifyManager {
// 注意:这里不自动切换,因为可能会导致中途渲染结果不一致
}
}
setRealtimeUpdater(realtimeUpdater) {
this.realtimeUpdater = realtimeUpdater;
}
/**
* 重置液化操作
* @returns {ImageData} 重置后的图像数据
@@ -519,7 +576,7 @@ export class EnhancedLiquifyManager {
// 检查图层是否为空
let objectsToCheck = [];
if (layer.isBackground || layer.type === "background") {
if (layer.isBackground || layer.type === "background" || layer.isFixed) {
// 背景图层使用 fabricObject (单数)
if (layer.fabricObject) {
objectsToCheck = [layer.fabricObject];
@@ -548,7 +605,10 @@ export class EnhancedLiquifyManager {
objectsToCheck[0].type === "rasterized-layer");
// 检查是否为组
const isGroup = objectsToCheck.some((obj) => obj.type === "group");
const isGroup =
objectsToCheck.some((obj) => obj.type === "group") ||
layer.type === LayerType.GROUP ||
layer.children?.length > 0;
// 如果不是单一图像,需要栅格化
const needsRasterization = !isImage || isGroup;
@@ -572,26 +632,34 @@ export class EnhancedLiquifyManager {
async _getImageData(fabricObject) {
return new Promise((resolve, reject) => {
try {
// 创建临时canvas
// 创建临时canvas - 关键修复使用原始图像尺寸不考虑fabric对象的缩放
const tempCanvas = document.createElement("canvas");
tempCanvas.width = fabricObject.width * fabricObject.scaleX;
tempCanvas.height = fabricObject.height * fabricObject.scaleY;
// 使用图像的原始尺寸,而不是缩放后的尺寸
tempCanvas.width = fabricObject.width;
tempCanvas.height = fabricObject.height;
const tempCtx = tempCanvas.getContext("2d");
// 如果对象有图像元素
if (fabricObject._element) {
// 绘制原始尺寸的图像
tempCtx.drawImage(
fabricObject._element,
0,
0,
tempCanvas.width,
tempCanvas.height
fabricObject.width,
fabricObject.height
);
} else if (fabricObject.getSrc) {
// 通过URL创建图像
const img = new Image();
img.onload = () => {
tempCtx.drawImage(img, 0, 0, tempCanvas.width, tempCanvas.height);
tempCtx.drawImage(
img,
0,
0,
fabricObject.width,
fabricObject.height
);
const imageData = tempCtx.getImageData(
0,
0,
@@ -615,6 +683,13 @@ export class EnhancedLiquifyManager {
tempCanvas.width,
tempCanvas.height
);
console.log(
`获取图像数据: 对象尺寸=${fabricObject.width}x${fabricObject.height}, ` +
`对象缩放=(${fabricObject.scaleX}, ${fabricObject.scaleY}), ` +
`图像数据尺寸=${imageData.width}x${imageData.height}`
);
resolve(imageData);
} catch (error) {
reject(error);

View File

@@ -0,0 +1,451 @@
/**
* 混合液化管理器 - 根据模式智能选择算法
*/
export class HybridLiquifyManager {
constructor(options = {}) {
this.config = {
gridSize: options.gridSize || 16,
maxStrength: options.maxStrength || 200,
smoothingIterations: options.smoothingIterations || 1,
relaxFactor: options.relaxFactor || 0.1,
};
this.params = {
size: 80,
pressure: 0.8,
distortion: 0,
power: 0.8,
};
this.modes = {
PUSH: "push",
CLOCKWISE: "clockwise",
COUNTERCLOCKWISE: "counterclockwise",
PINCH: "pinch",
EXPAND: "expand",
CRYSTAL: "crystal",
EDGE: "edge",
RECONSTRUCT: "reconstruct",
};
// 定义哪些模式使用像素算法
this.pixelModes = new Set([
this.modes.CLOCKWISE,
this.modes.COUNTERCLOCKWISE,
this.modes.CRYSTAL,
this.modes.EDGE,
]);
// 定义哪些模式使用网格算法
this.meshModes = new Set([
this.modes.PUSH,
this.modes.PINCH,
this.modes.EXPAND,
this.modes.RECONSTRUCT,
]);
this.currentMode = this.modes.PUSH;
this.originalImageData = null;
this.currentImageData = null;
this.mesh = null;
this.initialized = false;
this.canvas = document.createElement("canvas");
this.ctx = this.canvas.getContext("2d");
// 持续按压相关状态
this.pressStartTime = 0;
this.pressDuration = 0;
this.accumulatedRotation = 0;
this.accumulatedScale = 0;
this.isHolding = false;
this.continuousTimer = null;
// 鼠标状态
this.initialMouseX = 0;
this.initialMouseY = 0;
this.currentMouseX = 0;
this.currentMouseY = 0;
this.isDragging = false;
this.dragDistance = 0;
}
// ...existing initialization methods...
/**
* 应用液化变形 - 智能选择算法
*/
applyDeformation(x, y) {
if (!this.initialized || !this.originalImageData) {
return this.currentImageData;
}
// 更新鼠标位置
this.currentMouseX = x;
this.currentMouseY = y;
const { size, pressure, power } = this.params;
const radius = size;
const strength = pressure * power;
// 根据模式选择算法
if (this.pixelModes.has(this.currentMode)) {
return this._applyPixelDeformation(x, y, radius, strength);
} else if (this.meshModes.has(this.currentMode)) {
return this._applyMeshDeformation(x, y, radius, strength);
}
return this.currentImageData;
}
/**
* 像素级液化算法 - 适用于旋转、水晶、边缘模式
*/
_applyPixelDeformation(centerX, centerY, radius, strength) {
const data = this.currentImageData.data;
const width = this.currentImageData.width;
const height = this.currentImageData.height;
const tempData = new Uint8ClampedArray(data);
const processRadius = Math.min(radius, Math.min(width, height) / 2);
// 计算影响区域边界
const minX = Math.max(0, Math.floor(centerX - processRadius));
const maxX = Math.min(width, Math.ceil(centerX + processRadius));
const minY = Math.max(0, Math.floor(centerY - processRadius));
const maxY = Math.min(height, Math.ceil(centerY + processRadius));
switch (this.currentMode) {
case this.modes.CLOCKWISE:
case this.modes.COUNTERCLOCKWISE:
this._applyPixelRotation(
tempData,
data,
width,
height,
centerX,
centerY,
processRadius,
strength,
this.currentMode === this.modes.CLOCKWISE
);
break;
case this.modes.CRYSTAL:
this._applyPixelCrystal(
tempData,
data,
width,
height,
centerX,
centerY,
processRadius,
strength
);
break;
case this.modes.EDGE:
this._applyPixelEdge(
tempData,
data,
width,
height,
centerX,
centerY,
processRadius,
strength
);
break;
}
return this.currentImageData;
}
/**
* 像素级旋转算法
*/
_applyPixelRotation(
srcData,
dstData,
width,
height,
centerX,
centerY,
radius,
strength,
clockwise
) {
// 计算旋转角度
const timeFactor = Math.min(this.pressDuration / 1000, 5.0);
const baseRotationSpeed = 0.015;
const rotationAngle =
(clockwise ? 1 : -1) *
baseRotationSpeed *
strength *
(1.0 + timeFactor * 0.3);
this.accumulatedRotation += rotationAngle;
const minX = Math.max(0, Math.floor(centerX - radius));
const maxX = Math.min(width, Math.ceil(centerX + radius));
const minY = Math.max(0, Math.floor(centerY - radius));
const maxY = Math.min(height, Math.ceil(centerY + radius));
for (let y = minY; y < maxY; y++) {
for (let x = minX; x < maxX; x++) {
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < radius && distance > 0.1) {
// 距离衰减:内圈快,外圈慢
const normalizedDistance = distance / radius;
const falloff = Math.pow(1 - normalizedDistance, 2); // 二次衰减
// 计算旋转后的源位置
const angle = Math.atan2(dy, dx);
const newAngle = angle + this.accumulatedRotation * falloff;
const sourceX = centerX + Math.cos(newAngle) * distance;
const sourceY = centerY + Math.sin(newAngle) * distance;
// 双线性插值采样
const color = this._bilinearSample(
srcData,
width,
height,
sourceX,
sourceY
);
if (color) {
const targetIdx = (y * width + x) * 4;
dstData[targetIdx] = color[0];
dstData[targetIdx + 1] = color[1];
dstData[targetIdx + 2] = color[2];
dstData[targetIdx + 3] = color[3];
}
}
}
}
}
/**
* 像素级水晶效果
*/
_applyPixelCrystal(
srcData,
dstData,
width,
height,
centerX,
centerY,
radius,
strength
) {
const timeFactor = Math.min(this.pressDuration / 1000, 3.0);
const distortionStrength = strength * (1.0 + timeFactor * 0.5);
const minX = Math.max(0, Math.floor(centerX - radius));
const maxX = Math.min(width, Math.ceil(centerX + radius));
const minY = Math.max(0, Math.floor(centerY - radius));
const maxY = Math.min(height, Math.ceil(centerY + radius));
for (let y = minY; y < maxY; y++) {
for (let x = minX; x < maxX; x++) {
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < radius && distance > 0.1) {
const normalizedDistance = distance / radius;
const falloff = 1 - normalizedDistance * normalizedDistance;
const angle = Math.atan2(dy, dx);
const crystalRadius = normalizedDistance;
// 多层波浪扭曲
const wave1 = Math.sin(angle * 8 + this.pressDuration * 0.005) * 0.6;
const wave2 = Math.cos(angle * 12 + this.pressDuration * 0.003) * 0.4;
const waveAngle =
angle + (wave1 + wave2) * distortionStrength * falloff;
const radialMod =
1 +
Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) *
0.3;
const modDistance = distance * radialMod;
const sourceX = centerX + Math.cos(waveAngle) * modDistance;
const sourceY = centerY + Math.sin(waveAngle) * modDistance;
const color = this._bilinearSample(
srcData,
width,
height,
sourceX,
sourceY
);
if (color) {
const targetIdx = (y * width + x) * 4;
const factor = falloff * distortionStrength * 0.7;
// 混合原始颜色和扭曲颜色
const originalIdx = (y * width + x) * 4;
dstData[targetIdx] = Math.round(
srcData[originalIdx] * (1 - factor) + color[0] * factor
);
dstData[targetIdx + 1] = Math.round(
srcData[originalIdx + 1] * (1 - factor) + color[1] * factor
);
dstData[targetIdx + 2] = Math.round(
srcData[originalIdx + 2] * (1 - factor) + color[2] * factor
);
dstData[targetIdx + 3] = srcData[originalIdx + 3];
}
}
}
}
}
/**
* 像素级边缘效果
*/
_applyPixelEdge(
srcData,
dstData,
width,
height,
centerX,
centerY,
radius,
strength
) {
const timeFactor = Math.min(this.pressDuration / 1000, 2.5);
const edgeStrength = strength * (1.0 + timeFactor * 0.4);
const minX = Math.max(0, Math.floor(centerX - radius));
const maxX = Math.min(width, Math.ceil(centerX + radius));
const minY = Math.max(0, Math.floor(centerY - radius));
const maxY = Math.min(height, Math.ceil(centerY + radius));
for (let y = minY; y < maxY; y++) {
for (let x = minX; x < maxX; x++) {
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < radius && distance > 0.1) {
const normalizedDistance = distance / radius;
const falloff = 1 - normalizedDistance * normalizedDistance;
const angle = Math.atan2(dy, dx);
const edgeRadius = normalizedDistance;
const edgeWave =
Math.sin(edgeRadius * Math.PI * 4 + this.pressDuration * 0.004) *
Math.cos(angle * 6 + this.pressDuration * 0.002);
const perpAngle = angle + Math.PI / 2;
const edgeFactor = edgeWave * falloff * edgeStrength * 0.5;
const offsetX = Math.cos(perpAngle) * edgeFactor;
const offsetY = Math.sin(perpAngle) * edgeFactor;
const sourceX = x + offsetX;
const sourceY = y + offsetY;
const color = this._bilinearSample(
srcData,
width,
height,
sourceX,
sourceY
);
if (color) {
const targetIdx = (y * width + x) * 4;
dstData[targetIdx] = color[0];
dstData[targetIdx + 1] = color[1];
dstData[targetIdx + 2] = color[2];
dstData[targetIdx + 3] = color[3];
}
}
}
}
}
/**
* 双线性插值采样
*/
_bilinearSample(data, width, height, x, y) {
if (x < 0 || x >= width - 1 || y < 0 || y >= height - 1) {
return null;
}
const x1 = Math.floor(x);
const y1 = Math.floor(y);
const x2 = x1 + 1;
const y2 = y1 + 1;
const fx = x - x1;
const fy = y - y1;
const getPixel = (px, py) => {
const idx = (py * width + px) * 4;
return [data[idx], data[idx + 1], data[idx + 2], data[idx + 3]];
};
const p1 = getPixel(x1, y1);
const p2 = getPixel(x2, y1);
const p3 = getPixel(x1, y2);
const p4 = getPixel(x2, y2);
return [
Math.round(
(1 - fx) * (1 - fy) * p1[0] +
fx * (1 - fy) * p2[0] +
(1 - fx) * fy * p3[0] +
fx * fy * p4[0]
),
Math.round(
(1 - fx) * (1 - fy) * p1[1] +
fx * (1 - fy) * p2[1] +
(1 - fx) * fy * p3[1] +
fx * fy * p4[1]
),
Math.round(
(1 - fx) * (1 - fy) * p1[2] +
fx * (1 - fy) * p2[2] +
(1 - fx) * fy * p3[2] +
fx * fy * p4[2]
),
Math.round(
(1 - fx) * (1 - fy) * p1[3] +
fx * (1 - fy) * p2[3] +
(1 - fx) * fy * p3[3] +
fx * fy * p4[3]
),
];
}
/**
* 网格液化算法 - 适用于推拉、捏合、展开模式
*/
_applyMeshDeformation(x, y, radius, strength) {
if (!this.mesh) return this.currentImageData;
// 使用现有的网格算法处理推拉、捏合、展开
const mode = this.currentMode;
const { distortion } = this.params;
this._applyDeformation(x, y, radius, strength, mode, distortion);
if (this.config.smoothingIterations > 0) {
this._smoothMesh();
}
return this._applyMeshToImage();
}
// ...existing mesh methods...
}

View File

@@ -38,7 +38,7 @@ export class LiquifyManager {
meshResolution: options.meshResolution || 64,
// 根据环境选择合适的渲染模式
forceCPU: true, // 默认不强制使用CPU
forceWebGL: false, // 优先使用WebGL模式
forceWebGL: true, // 优先使用WebGL模式
webglSizeThreshold: options.webglSizeThreshold || 500 * 500, // 降低阈值以更倾向使用WebGL
layerManager: options.layerManager || null,
canvas: options.canvas || null,
@@ -87,9 +87,19 @@ export class LiquifyManager {
* @param {Number} value 参数值
*/
setParam(param, value) {
console.log(`LiquifyManager 设置参数: ${param}=${value}`);
return this.enhancedManager.setParam(param, value);
}
/**
* 批量设置参数
* @param {Object} params 参数对象
*/
setParams(params) {
console.log("LiquifyManager 批量设置参数:", params);
return this.enhancedManager.setParams(params);
}
/**
* 获取当前参数
* @returns {Object} 当前参数对象
@@ -120,21 +130,14 @@ export class LiquifyManager {
return null;
}
// 确保设置正确的模式和参数
if (mode) {
this.enhancedManager.setMode(mode);
}
console.log(
`LiquifyManager.applyLiquify: 模式=${mode}, 坐标=(${x}, ${y}), 参数=`,
params
);
if (params) {
Object.entries(params).forEach(([key, value]) => {
this.enhancedManager.setParam(key, value);
});
}
// 应用液化变形
console.log(`应用液化变形, 模式=${mode}, 坐标=(${x}, ${y}), 参数=`, params);
try {
// 直接调用EnhancedLiquifyManager的applyLiquify方法
// 避免重复设置参数让EnhancedLiquifyManager处理参数设置
const resultData = await this.enhancedManager.applyLiquify(
targetObject,
mode,
@@ -146,6 +149,13 @@ export class LiquifyManager {
// 确保返回结果数据
if (!resultData) {
console.warn("液化变形没有返回结果数据");
} else {
console.log(
"✅ 液化变形成功,返回图像数据尺寸:",
resultData.width,
"x",
resultData.height
);
}
return resultData;
@@ -180,6 +190,22 @@ export class LiquifyManager {
return this.enhancedManager.getStatus();
}
/**
* 开始液化操作(记录初始点)
* @param {Number} x 初始X坐标
* @param {Number} y 初始Y坐标
*/
startLiquifyOperation(x, y) {
return this.enhancedManager.startLiquifyOperation(x, y);
}
/**
* 结束液化操作
*/
endLiquifyOperation() {
return this.enhancedManager.endLiquifyOperation();
}
/**
* 释放资源
*/

View File

@@ -0,0 +1,443 @@
/**
* 液化实时更新器
* 负责高效地更新液化效果到画布上避免频繁创建fabric对象导致的性能问题
*/
export class LiquifyRealTimeUpdater {
constructor(canvas, options = {}) {
this.canvas = canvas;
this.targetObject = null;
this.isUpdating = false;
this.updateQueue = [];
this.lastUpdateTime = 0;
this.currImage = options.currImage || { value: null };
// 配置选项
this.config = {
throttleTime: options.throttleTime || 16, // 60fps
maxQueueSize: options.maxQueueSize || 5,
useDirectUpdate: options.useDirectUpdate !== false, // 默认启用直接更新
imageQuality: options.imageQuality || 1.0, // 图像质量 (0.1-1.0)
skipRenderDuringDrag: options.skipRenderDuringDrag || false, // 拖拽时跳过渲染
};
// 临时canvas用于快速渲染
this.tempCanvas = document.createElement("canvas");
this.tempCtx = this.tempCanvas.getContext("2d");
// 高质量canvas用于最终输出
this.highQualityCanvas = document.createElement("canvas");
this.highQualityCtx = this.highQualityCanvas.getContext("2d");
// 当前缓存的图像数据
this.cachedDataURL = null;
this.pendingImageData = null;
this.renderingScheduled = false;
// 优化Canvas画布渲染设置
this.canvas.renderOnAddRemove = false; // 禁用自动渲染
this.canvas.skipOffscreen = true; // 跳过离屏元素渲染
}
/**
* 设置目标对象
* @param {Object} fabricObject fabric图像对象
*/
setTargetObject(fabricObject) {
this.targetObject = fabricObject;
if (fabricObject && fabricObject._element) {
// 设置临时canvas尺寸
this.tempCanvas.width = fabricObject.width;
this.tempCanvas.height = fabricObject.height;
// 设置高质量canvas尺寸
this.highQualityCanvas.width = fabricObject.width;
this.highQualityCanvas.height = fabricObject.height;
// 配置高质量渲染上下文
this.highQualityCtx.imageSmoothingEnabled = true;
this.highQualityCtx.imageSmoothingQuality = "high";
// 配置临时canvas上下文快速渲染
this.tempCtx.imageSmoothingEnabled = false; // 快速模式关闭平滑
}
}
/**
* 实时更新图像数据到画布
* @param {ImageData} imageData 新的图像数据
* @param {Boolean} isDrawing 是否正在绘制(拖拽过程中)
* @returns {Promise} 更新完成的Promise
*/
async updateImage(imageData, isDrawing = false) {
if (!this.targetObject || !imageData) {
return;
}
// 节流控制
const now = Date.now();
if (now - this.lastUpdateTime < this.config.throttleTime && isDrawing) {
// 在绘制过程中进行节流,缓存最新的图像数据
this.pendingImageData = imageData;
return;
}
this.lastUpdateTime = now;
if (isDrawing && this.config.useDirectUpdate) {
// 拖拽过程中使用快速更新(降低质量以提高性能)
this._fastUpdate(imageData);
} else {
// 拖拽结束后使用完整更新(最高质量)
await this._fullUpdate(imageData);
}
}
/**
* 智能图像质量更新
* 根据图像尺寸和设备性能动态调整质量
* @param {ImageData} imageData 图像数据
* @param {Boolean} isDrawing 是否正在绘制
* @private
*/
_getOptimalQuality(imageData, isDrawing) {
const pixelCount = imageData.width * imageData.height;
if (isDrawing) {
// 拖拽时根据图像大小调整质量
if (pixelCount > 1000000) {
// 大于1M像素
return 0.7;
} else if (pixelCount > 500000) {
// 大于500K像素
return 0.8;
} else {
return 0.9;
}
} else {
// 拖拽结束时始终使用最高质量
return 1.0;
}
}
/**
* 快速更新 - 直接修改现有对象的图像源
* @param {ImageData} imageData 图像数据
* @private
*/
_fastUpdate(imageData) {
if (!this.targetObject || !this.targetObject._element) {
return;
}
try {
// 将ImageData渲染到临时canvas快速模式
this.tempCtx.putImageData(imageData, 0, 0);
// 获取智能质量设置
const quality = this._getOptimalQuality(imageData, true);
// 直接更新fabric对象的图像源使用PNG格式保持质量
const targetElement = this.targetObject._element;
// 方案1: 直接设置src属性最高性能
const dataURL = this.tempCanvas.toDataURL("image/png", quality);
if (targetElement.src !== dataURL) {
targetElement.src = dataURL;
// 关键优化直接设置fabric对象为脏状态但不立即渲染
// this.targetObject.dirty = false; // 标记为不需要立即渲染
// this.canvas.renderOnAddRemove = true; // 恢复自动渲染
// this.renderingScheduled = false; // 重置渲染调度状态
this?.scheduleRender?.(); // 调度一次渲染
// 使用requestAnimationFrame进行批量渲染优化
// if (!this.renderingScheduled && !this.config.skipRenderDuringDrag) {
// this.renderingScheduled = true;
// requestIdleCallback(() => {
// this.canvas.renderAll();
// this.renderingScheduled = false;
// });
// }
} else {
console.warn(
"=================快速更新液化效果时,图像数据未变化,跳过更新"
);
}
} catch (error) {
console.error("快速更新液化效果失败:", error);
}
}
getImageData(imageData) {
// 使用高质量canvas进行最终渲染
this.highQualityCtx.putImageData(imageData, 0, 0);
// 生成高质量DataURLPNG格式最大质量
const dataURL = this.highQualityCanvas.toDataURL("image/png", 1.0);
return dataURL;
}
/**
* 完整更新 - 创建新的fabric对象
* @param {ImageData} imageData 图像数据
* @private
*/
async _fullUpdate(imageData) {
return new Promise((resolve, reject) => {
try {
// 使用高质量canvas进行最终渲染
this.highQualityCtx.putImageData(imageData, 0, 0);
// 生成高质量DataURLPNG格式最大质量
const dataURL = this.highQualityCanvas.toDataURL("image/png", 1.0);
// 如果DataURL没有变化跳过更新
if (this.cachedDataURL === dataURL) {
resolve();
return;
}
this.cachedDataURL = dataURL;
// 创建新的fabric图像对象保持最高质量
fabric.Image.fromURL(
dataURL,
(newImg) => {
try {
if (!this.targetObject) {
console.warn("目标对象为空,跳过更新");
resolve();
return;
}
// 保存原对象信息用于智能查找
const originalObjId = this.targetObject.id;
const originalObjLayerId = this.targetObject.layerId;
// 保留原对象的所有变换属性
const originalObj = this.targetObject;
newImg.set({
left: originalObj.left,
top: originalObj.top,
scaleX: originalObj.scaleX,
scaleY: originalObj.scaleY,
angle: originalObj.angle,
flipX: originalObj.flipX,
flipY: originalObj.flipY,
opacity: originalObj.opacity,
originX: originalObj.originX,
originY: originalObj.originY,
id: originalObj.id,
name: originalObj.name,
layerId: originalObj.layerId,
selected: false,
evented: originalObj.evented,
});
// 临时禁用画布自动渲染
const oldRenderOnAddRemove = this.canvas.renderOnAddRemove;
this.canvas.renderOnAddRemove = false;
// 智能查找和替换canvas上的对象
const allObjects = this.canvas.getObjects();
let targetIndex = allObjects.indexOf(originalObj);
// 如果直接查找失败尝试通过ID查找
if (targetIndex === -1 && originalObjId) {
targetIndex = allObjects.findIndex(
(obj) => obj.id === originalObjId
);
if (targetIndex !== -1) {
console.log(`通过ID找到目标对象: ${originalObjId}`);
// 更新目标对象引用
this.targetObject = allObjects[targetIndex];
}
}
// 如果通过ID查找仍然失败尝试通过图层ID查找
if (targetIndex === -1 && originalObjLayerId) {
targetIndex = allObjects.findIndex(
(obj) => obj.layerId === originalObjLayerId
);
if (targetIndex !== -1) {
console.log(`通过图层ID找到目标对象: ${originalObjLayerId}`);
// 更新目标对象引用
this.targetObject = allObjects[targetIndex];
}
}
if (targetIndex !== -1) {
// 找到目标对象,执行替换
this.canvas.remove(this.targetObject);
this.canvas.insertAt(newImg, targetIndex);
// 恢复自动渲染设置
this.canvas.renderOnAddRemove = oldRenderOnAddRemove;
// 更新目标对象引用
this.targetObject = newImg;
// 一次性重新渲染画布
this.canvas.renderAll();
console.log(`✅ 液化对象更新成功,位置: ${targetIndex}`);
resolve(newImg);
} else {
// 如果在画布中找不到对象,可能对象已被移除或引用已更新
console.warn(
"在画布中找不到目标对象,可能已被其他操作移除或替换"
);
// 恢复自动渲染设置
this.canvas.renderOnAddRemove = oldRenderOnAddRemove;
// 尝试添加新对象到画布末尾
this.canvas.add(newImg);
this.targetObject = newImg;
this.canvas.renderAll();
console.log("🔄 已将新对象添加到画布末尾");
resolve(newImg);
}
} catch (error) {
// 恢复自动渲染设置
this.canvas.renderOnAddRemove = oldRenderOnAddRemove;
console.error("更新fabric对象时出错:", error);
reject(error);
}
},
{ crossOrigin: "anonymous" }
); // 确保跨域支持
} catch (error) {
console.error("完整更新过程出错:", error);
reject(error);
}
});
}
/**
* 处理待处理的图像数据
* 在拖拽结束后调用,处理可能积压的更新
*/
async processPendingUpdates() {
if (this.pendingImageData && !this.isUpdating) {
this.isUpdating = true;
try {
await this._fullUpdate(this.pendingImageData);
this.pendingImageData = null;
} catch (error) {
console.error("处理待处理更新失败:", error);
} finally {
this.isUpdating = false;
}
}
}
/**
* 清理资源
*/
dispose() {
this.targetObject = null;
this.cachedDataURL = null;
this.pendingImageData = null;
this.updateQueue.length = 0;
// 清理临时canvas
if (this.tempCanvas) {
this.tempCanvas.width = 0;
this.tempCanvas.height = 0;
this.tempCanvas = null;
this.tempCtx = null;
}
}
/**
* 获取当前目标对象
* @returns {Object} 当前的fabric对象
*/
getTargetObject() {
return this.targetObject;
}
/**
* 强制进行完整更新
* @param {ImageData} imageData 图像数据
*/
async forceFullUpdate(imageData) {
return this._fullUpdate(imageData);
}
/**
* 启用拖拽模式 - 暂停渲染以提高性能
*/
enableDragMode() {
this.config.skipRenderDuringDrag = true;
this.canvas.renderOnAddRemove = false;
console.log("🚀 启用拖拽优化模式");
}
/**
* 禁用拖拽模式 - 恢复正常渲染
*/
disableDragMode() {
this.config.skipRenderDuringDrag = false;
this.canvas.renderOnAddRemove = true;
// 执行一次完整渲染
this.canvas.renderAll();
console.log("✅ 恢复正常渲染模式");
}
/**
* 获取当前目标对象
*/
getTargetObject() {
return this.targetObject;
}
/**
* 设置图像质量
* @param {Number} quality 质量值 (0.1-1.0)
*/
setImageQuality(quality) {
this.config.imageQuality = Math.max(0.1, Math.min(1.0, quality));
}
/**
* 优化的批量渲染方法
*/
scheduleRender() {
if (!this.renderingScheduled) {
this.renderingScheduled = true;
requestAnimationFrame(() => {
this.canvas.renderAll();
this.renderingScheduled = false;
});
}
}
/**
* 清理资源
*/
dispose() {
// 恢复canvas设置
this.canvas.renderOnAddRemove = true;
// 清理缓存
this.cachedDataURL = null;
this.pendingImageData = null;
// 清理canvas
if (this.tempCanvas) {
this.tempCanvas.width = 0;
this.tempCanvas.height = 0;
}
if (this.highQualityCanvas) {
this.highQualityCanvas.width = 0;
this.highQualityCanvas.height = 0;
}
console.log("🧹 液化实时更新器资源已清理");
}
}

View File

@@ -0,0 +1,431 @@
/**
* 液化面板状态管理器
* 负责管理液化操作的状态、性能优化和用户反馈
*/
export class LiquifyStateManager {
constructor(canvas, realtimeUpdater) {
this.canvas = canvas;
this.realtimeUpdater = realtimeUpdater;
// 状态管理
this.isOperating = false;
this.isDragging = false;
this.operationCount = 0;
this.startTime = null;
// 性能监控
this.performanceMetrics = {
totalOperations: 0,
totalTime: 0,
averageTime: 0,
maxTime: 0,
minTime: Infinity,
lastOperationTime: 0,
};
// 用户反馈
this.feedbackEnabled = true;
this.cursorCache = new Map();
// 设备性能检测
this.devicePerformance = this._detectDevicePerformance();
console.log(
"🎯 液化状态管理器已初始化,设备性能等级:",
this.devicePerformance
);
}
/**
* 开始液化操作
*/
startOperation() {
if (this.isOperating) return;
this.isOperating = true;
this.startTime = performance.now();
// 根据设备性能调整设置
this._adjustPerformanceSettings();
// 显示操作反馈
this._showOperationFeedback();
console.log("🚀 开始液化操作");
}
/**
* 开始拖拽
*/
startDrag() {
if (this.isDragging) return;
this.isDragging = true;
// 优化拖拽性能
this.realtimeUpdater?.enableDragMode();
// 更新鼠标样式
this._updateCursor("liquifying");
// 禁用不必要的画布功能
this._disableCanvasFeatures();
console.log("🖱️ 开始拖拽操作");
}
/**
* 结束拖拽
*/
async endDrag() {
if (!this.isDragging) return;
// 恢复鼠标样式
this._updateCursor("default");
// 恢复画布功能
this._enableCanvasFeatures();
// 处理待处理的更新
if (this.realtimeUpdater) {
try {
await this.realtimeUpdater.processPendingUpdates();
} catch (error) {
console.error("处理待处理更新失败:", error);
} finally {
this.isDragging = false;
// 恢复正常模式
this.realtimeUpdater?.disableDragMode();
// 结束液化操作 添加结果到命令中 更新当前激活图层对象
}
}
console.log("✅ 结束拖拽操作");
}
/**
* 结束液化操作
*/
endOperation() {
if (!this.isOperating) return;
const operationTime = performance.now() - this.startTime;
this._updatePerformanceMetrics(operationTime);
this.isOperating = false;
this.operationCount++;
// 隐藏操作反馈
this._hideOperationFeedback();
console.log(`⏱️ 液化操作完成,耗时: ${operationTime.toFixed(2)}ms`);
}
/**
* 记录单次变形操作
*/
recordDeformation(operationTime) {
this.performanceMetrics.totalOperations++;
this.performanceMetrics.totalTime += operationTime;
this.performanceMetrics.averageTime =
this.performanceMetrics.totalTime /
this.performanceMetrics.totalOperations;
this.performanceMetrics.maxTime = Math.max(
this.performanceMetrics.maxTime,
operationTime
);
this.performanceMetrics.minTime = Math.min(
this.performanceMetrics.minTime,
operationTime
);
this.performanceMetrics.lastOperationTime = operationTime;
}
/**
* 记录操作性能指标
* @param {Object} metrics 性能指标对象
*/
recordOperationMetrics(metrics) {
const {
operationTime,
operationType,
mode,
coordinates,
imageSize,
renderMode,
isRealTime,
} = metrics;
// 记录基础性能数据
this.recordDeformation(operationTime);
// 记录详细操作信息
this.performanceMetrics.lastOperation = {
type: operationType,
mode,
coordinates,
imageSize,
renderMode,
isRealTime,
timestamp: Date.now(),
};
// 根据性能数据动态调整设置
this._adaptivePerformanceOptimization(operationTime);
console.log(
`📊 记录性能指标: ${operationType}/${mode}, 耗时: ${operationTime.toFixed(
2
)}ms, 渲染模式: ${renderMode}`
);
}
/**
* 自适应性能优化
* @param {Number} operationTime 操作耗时
* @private
*/
_adaptivePerformanceOptimization(operationTime) {
if (!this.realtimeUpdater) return;
// 如果操作耗时过长,动态降低质量或增加节流时间
if (operationTime > 50 && this.devicePerformance !== "high") {
// 降低图像质量
const currentQuality = this.realtimeUpdater.config.imageQuality || 1.0;
if (currentQuality > 0.7) {
this.realtimeUpdater.setImageQuality(
Math.max(0.7, currentQuality - 0.1)
);
console.log("⚡ 自动降低图像质量以提升性能");
}
// 增加节流时间
if (this.realtimeUpdater.config.throttleTime < 33) {
this.realtimeUpdater.config.throttleTime = Math.min(
33,
this.realtimeUpdater.config.throttleTime + 8
);
console.log("⏱️ 自动增加节流时间以提升性能");
}
}
// 如果性能很好,可以适当提高质量
if (operationTime < 20 && this.devicePerformance === "high") {
const currentQuality = this.realtimeUpdater.config.imageQuality || 1.0;
if (currentQuality < 1.0) {
this.realtimeUpdater.setImageQuality(
Math.min(1.0, currentQuality + 0.05)
);
}
}
}
/**
* 获取性能报告
*/
getPerformanceReport() {
return {
...this.performanceMetrics,
devicePerformance: this.devicePerformance,
fps: this._calculateFPS(),
recommendations: this._generateRecommendations(),
};
}
/**
* 设置用户反馈
*/
setFeedbackEnabled(enabled) {
this.feedbackEnabled = enabled;
}
/**
* 清理资源
*/
dispose() {
this._enableCanvasFeatures();
this._updateCursor("default");
this.cursorCache.clear();
console.log("🧹 液化状态管理器已清理");
}
// === 私有方法 ===
/**
* 检测设备性能
*/
_detectDevicePerformance() {
// 检测硬件并发数
const cores = navigator.hardwareConcurrency || 4;
// 检测内存
const memory = navigator.deviceMemory || 4;
// 检测连接类型
const connection = navigator.connection;
const effectiveType = connection?.effectiveType || "4g";
// 简单的性能评分算法
let score = 0;
score += cores * 10;
score += memory * 5;
if (effectiveType === "4g") score += 10;
else if (effectiveType === "3g") score += 5;
// 性能等级
if (score >= 70) return "high";
if (score >= 40) return "medium";
return "low";
}
/**
* 根据设备性能调整设置
*/
_adjustPerformanceSettings() {
if (!this.realtimeUpdater) return;
switch (this.devicePerformance) {
case "high":
this.realtimeUpdater.setImageQuality(1.0);
this.realtimeUpdater.config.throttleTime = 8; // 120fps
break;
case "medium":
this.realtimeUpdater.setImageQuality(0.9);
this.realtimeUpdater.config.throttleTime = 16; // 60fps
break;
case "low":
this.realtimeUpdater.setImageQuality(0.8);
this.realtimeUpdater.config.throttleTime = 33; // 30fps
break;
}
}
/**
* 显示操作反馈
*/
_showOperationFeedback() {
if (!this.feedbackEnabled) return;
// 添加视觉反馈(可以是加载动画、进度条等)
document.body.style.cursor = "wait";
}
/**
* 隐藏操作反馈
*/
_hideOperationFeedback() {
if (!this.feedbackEnabled) return;
document.body.style.cursor = "default";
}
/**
* 更新鼠标样式
*/
_updateCursor(type) {
if (!this.feedbackEnabled) return;
const cursors = {
default: "default",
liquifying: "crosshair",
wait: "wait",
"not-allowed": "not-allowed",
};
const cursor = cursors[type] || "default";
if (this.canvas && this.canvas.upperCanvasEl) {
this.canvas.upperCanvasEl.style.cursor = cursor;
}
}
/**
* 禁用画布功能以提高性能
*/
_disableCanvasFeatures() {
if (!this.canvas) return;
// 保存原始设置
this._originalSettings = {
renderOnAddRemove: this.canvas.renderOnAddRemove,
skipOffscreen: this.canvas.skipOffscreen,
enableRetinaScaling: this.canvas.enableRetinaScaling,
};
// 应用性能优化设置
this.canvas.renderOnAddRemove = false;
this.canvas.skipOffscreen = true;
// 低性能设备关闭高分辨率支持
if (this.devicePerformance === "low") {
this.canvas.enableRetinaScaling = false;
}
}
/**
* 恢复画布功能
*/
_enableCanvasFeatures() {
if (!this.canvas || !this._originalSettings) return;
// 恢复原始设置
this.canvas.renderOnAddRemove = this._originalSettings.renderOnAddRemove;
this.canvas.skipOffscreen = this._originalSettings.skipOffscreen;
this.canvas.enableRetinaScaling =
this._originalSettings.enableRetinaScaling;
this._originalSettings = null;
}
/**
* 更新性能指标
*/
_updatePerformanceMetrics(operationTime) {
this.performanceMetrics.totalTime += operationTime;
this.performanceMetrics.averageTime =
this.performanceMetrics.totalTime / (this.operationCount + 1);
this.performanceMetrics.maxTime = Math.max(
this.performanceMetrics.maxTime,
operationTime
);
this.performanceMetrics.minTime = Math.min(
this.performanceMetrics.minTime,
operationTime
);
}
/**
* 计算FPS
*/
_calculateFPS() {
if (this.performanceMetrics.averageTime === 0) return 0;
return Math.round(1000 / this.performanceMetrics.averageTime);
}
/**
* 生成性能建议
*/
_generateRecommendations() {
const recommendations = [];
if (this.performanceMetrics.averageTime > 50) {
recommendations.push("操作响应较慢,建议降低图像尺寸或关闭高质量模式");
}
if (this.devicePerformance === "low") {
recommendations.push("检测到低性能设备,已自动启用性能优化模式");
}
const fps = this._calculateFPS();
if (fps < 30) {
recommendations.push("帧率较低,建议减少同时进行的操作或降低液化强度");
}
return recommendations;
}
}