Files
aida_front/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyCPUManager.js
2026-01-02 11:24:11 +08:00

1399 lines
42 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.
/**
* CPU版本的液化管理器
* 修复版本 - 解决三角形网格失真问题,优化持续按压效果
*/
export class LiquifyCPUManager {
constructor(options = {}) {
this.config = {
gridSize: 8, // 稍微增大网格提高性能
maxStrength: 200, // 适度降低最大强度
smoothingIterations: 1, // 增加平滑处理
relaxFactor: 0.05, // 适度松弛
sharpenAmount: 0.3, // 添加锐化强度参数
...options,
};
console.log("CPU版本的液化管理器config", this.config);
this.params = {
size: 60, // 增大默认尺寸
pressure: 0.6, // 增大默认压力
distortion: 0,
power: 0.7, // 增大默认动力
};
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.mesh = null;
this.initialized = false;
this.canvas = document.createElement("canvas");
this.ctx = this.canvas.getContext("2d");
this.deformHistory = [];
// 性能优化相关
this.lastUpdateTime = 0;
this.updateThrottle = 16; // 限制更新频率约60fps
this.isProcessing = false;
// 鼠标位置跟踪(用于推拉模式)
this.initialMouseX = 0; // 初始点击位置X
this.initialMouseY = 0; // 初始点击位置Y
this.currentMouseX = 0; // 当前鼠标位置X
this.currentMouseY = 0; // 当前鼠标位置Y
this.lastMouseX = 0;
this.lastMouseY = 0;
this.mouseMovementX = 0;
this.mouseMovementY = 0;
this.isFirstApply = true; // 标记是否是首次应用
this.isDragging = false; // 标记是否正在拖拽
this.dragDistance = 0; // 拖拽距离
this.dragAngle = 0; // 拖拽角度
// 新增:持续按压相关状态
this.pressStartTime = 0; // 按压开始时间
this.pressDuration = 0; // 按压持续时间
this.accumulatedRotation = 0; // 累积旋转角度(用于顺时针/逆时针)--废除使用固定角度
this.fixedRotationAngle = 0.32; // 固定旋转角度
this.accumulatedScale = 0; // 累积缩放量(用于捏合/展开)
this.lastApplyTime = 0; // 上次应用时间
this.continuousApplyInterval = 50; // 持续应用间隔(毫秒)
this.isHolding = false; // 是否正在持续按压
}
initialize(imageSource) {
try {
if (imageSource instanceof ImageData) {
this.originalImageData = new ImageData(
new Uint8ClampedArray(imageSource.data),
imageSource.width,
imageSource.height
);
} else if (imageSource instanceof HTMLImageElement) {
this.canvas.width = imageSource.width;
this.canvas.height = imageSource.height;
this.ctx.drawImage(imageSource, 0, 0);
this.originalImageData = this.ctx.getImageData(0, 0, imageSource.width, imageSource.height);
} else {
throw new Error("不支持的图像类型");
}
this.currentImageData = new ImageData(
new Uint8ClampedArray(this.originalImageData.data),
this.originalImageData.width,
this.originalImageData.height
);
this._initMesh(this.originalImageData.width, this.originalImageData.height);
this.initialized = true;
return true;
} catch (error) {
console.error("液化管理器初始化失败:", error);
return false;
}
}
_initMesh(width, height) {
const gridSize = this.config.gridSize;
const cols = Math.ceil(width / gridSize);
const rows = Math.ceil(height / gridSize);
this.mesh = {
cols,
rows,
gridSize,
width,
height,
originalPoints: [],
deformedPoints: [],
};
for (let y = 0; y <= rows; y++) {
for (let x = 0; x <= cols; x++) {
const point = { x: x * gridSize, y: y * gridSize };
this.mesh.originalPoints.push({ ...point });
this.mesh.deformedPoints.push({ ...point });
}
}
}
setMode(mode) {
if (Object.values(this.modes).includes(mode)) {
this.currentMode = mode;
return true;
}
return false;
}
setParam(param, value) {
if (param === 'sharpness') {
this.config.sharpenAmount = Math.max(0, Math.min(1, value));
return true;
}
if (param in this.params) {
this.params[param] = value;
return true;
}
return false;
}
getParams() {
return { ...this.params, sharpness: this.config.sharpenAmount, };
}
// 添加清晰度控制方法
setSharpness(amount) {
this.config.sharpenAmount = Math.max(0, Math.min(1, amount));
return this;
}
resetParams() {
this.params = {
size: 60, // 增大默认尺寸
pressure: 0.6, // 增大默认压力
distortion: 0,
power: 0.7, // 增大默认动力
};
}
/**
* 开始液化操作(记录初始点)
* @param {Number} x 初始X坐标
* @param {Number} y 初始Y坐标
*/
startDeformation(x, y) {
this.initialMouseX = x;
this.initialMouseY = y;
this.currentMouseX = x;
this.currentMouseY = y;
this.lastMouseX = x;
this.lastMouseY = y;
this.isDragging = true;
this.isFirstApply = true;
this.dragDistance = 0;
this.dragAngle = 0;
// 新增:初始化持续按压状态
this.pressStartTime = Date.now();
this.pressDuration = 0;
this.accumulatedRotation = 0;
this.accumulatedScale = 0;
this.lastApplyTime = this.pressStartTime;
this.isHolding = true;
// 启动持续效果定时器(对于所有模式都支持持续按压)
// this.startContinuousEffect();
console.log(`开始液化操作,初始点: (${x}, ${y})`);
}
/**
* 结束液化操作
*/
endDeformation() {
this.isDragging = false;
this.isFirstApply = true;
this.dragDistance = 0;
this.dragAngle = 0;
// 新增:重置持续按压状态
this.isHolding = false;
this.pressStartTime = 0;
this.pressDuration = 0;
this.accumulatedRotation = 0;
this.accumulatedScale = 0;
this.lastApplyTime = 0;
// 停止持续效果定时器
this.stopContinuousEffect();
console.log("结束液化操作");
}
// 新增:启动持续效果
startContinuousEffect() {
this.stopContinuousEffect(); // 先停止已有的定时器
this.continuousTimer = setInterval(() => {
if (this.isHolding && this.initialized) {
// 更新持续时间
this.pressDuration = Date.now() - this.pressStartTime;
// 所有模式都支持持续效果
this.applyContinuousDeformation();
}
}, this.continuousApplyInterval);
}
// 新增:停止持续效果
stopContinuousEffect() {
if (this.continuousTimer) {
clearInterval(this.continuousTimer);
this.continuousTimer = null;
}
}
/**
* 稳定的旋转衰减函数 - 确保内圈快外圈慢,保持纹理连续性
* @param {number} t 归一化距离 (0-1)
* @returns {number} 衰减因子 (0-1)
*/
_stableRotationFalloff(t) {
if (t >= 1.0) return 0;
if (t <= 0) return 1;
// 使用反向二次函数:内圈(t=0)时值为1外圈(t=1)时值为0
// 这确保了内圈旋转最快,外圈旋转最慢
const inverseFalloff = 1 - t;
// 使用平滑的二次衰减,确保内圈效果强,外圈效果弱
const quadraticFalloff = inverseFalloff * inverseFalloff;
// 添加轻微的线性分量,确保过渡平滑
const linearFalloff = inverseFalloff;
// 混合二次和线性衰减70%二次衰减 + 30%线性衰减
return quadraticFalloff * 0.7 + linearFalloff * 0.3;
}
/**
* 基于test-liquify-enhanced.html的旋转算法 - 像素级实现
* @param {number} centerX 旋转中心X坐标
* @param {number} centerY 旋转中心Y坐标
* @param {number} radius 影响半径
* @param {number} strength 强度
* @param {boolean} isClockwise 是否顺时针旋转
*/
_applyEnhancedRotationDeformation(centerX, centerY, radius, strength, isClockwise) {
if (!this.currentImageData) return;
const data = this.currentImageData.data;
const width = this.currentImageData.width;
const height = this.currentImageData.height;
const tempData = new Uint8ClampedArray(data);
// 计算旋转角度 - 基于test-liquify-enhanced.html的算法
const { pressure, power } = this.params;
const timeFactor = Math.min(this.pressDuration / 1000, 5.0);
const baseRotationSpeed = 0.02; // 使用与测试文件相同的速度
const rotationAngle =
(isClockwise ? 1 : -1) * baseRotationSpeed * pressure * power * (1.0 + timeFactor * 0.5);
console.log("持续应用旋转效果");
// 累积旋转角度 - 关键:这确保了持续旋转效果
this.accumulatedRotation += rotationAngle;
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));
// 遍历影响区域内的每个像素
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 < processRadius && distance > 0.1) {
// 距离衰减:内圈快,外圈慢 - 与测试文件算法一致
const normalizedDistance = distance / processRadius;
const falloff = Math.pow(1 - normalizedDistance, 2); // 二次衰减
// 计算旋转后的源位置 - 关键算法
const angle = Math.atan2(dy, dx);
// const newAngle = angle + this.accumulatedRotation * falloff;
const newAngle = angle + (isClockwise ? this.fixedRotationAngle : -this.fixedRotationAngle) * falloff;
const sourceX = centerX + Math.cos(newAngle) * distance;
const sourceY = centerY + Math.sin(newAngle) * distance;
// 双线性插值采样 - 确保像素连续性
const color = this._bicubicInterpolate(tempData, width, height, sourceX, sourceY);
if (color) {
const targetIdx = (y * width + x) * 4;
data[targetIdx] = color[0];
data[targetIdx + 1] = color[1];
data[targetIdx + 2] = color[2];
data[targetIdx + 3] = color[3];
}
}
}
}
return true;
}
/**
* 基于test-liquify-enhanced.html的捏合/展开算法
* @param {number} centerX 中心X坐标
* @param {number} centerY 中心Y坐标
* @param {number} radius 影响半径
* @param {number} strength 强度
* @param {boolean} isPinch 是否为捏合模式
*/
_applyEnhancedPinchDeformation(centerX, centerY, radius, strength, isPinch) {
if (!this.currentImageData) return;
const data = this.currentImageData.data;
const width = this.currentImageData.width;
const height = this.currentImageData.height;
const tempData = new Uint8ClampedArray(data);
// 计算时间相关的缩放因子 - 基于test-liquify-enhanced.html
const timeFactor = Math.min(this.pressDuration / 1000, 3.0);
const baseScaleFactor = isPinch ? -0.01 : 0.01;
const scaleFactor = baseScaleFactor * (1.0 + timeFactor * 0.5);
this.accumulatedScale += scaleFactor;
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));
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 < processRadius && distance > 0.1) {
const normalizedDistance = distance / processRadius;
const falloff = 1 - normalizedDistance * normalizedDistance;
// 计算缩放后的位置
const scale = 1 + this.accumulatedScale * falloff;
const sourceX = centerX + dx * scale;
const sourceY = centerY + dy * scale;
// 双线性插值采样
const color = this._bicubicInterpolate(tempData, width, height, sourceX, sourceY);
if (color) {
const targetIdx = (y * width + x) * 4;
data[targetIdx] = color[0];
data[targetIdx + 1] = color[1];
data[targetIdx + 2] = color[2];
data[targetIdx + 3] = color[3];
}
}
}
}
return true;
}
/**
* 基于test-liquify-enhanced.html的推拉算法
* @param {number} centerX 中心X坐标
* @param {number} centerY 中心Y坐标
* @param {number} radius 影响半径
* @param {number} strength 强度
*/
_applyEnhancedPushDeformation(centerX, centerY, radius, strength) {
if (!this.currentImageData) return;
const data = this.currentImageData.data;
const width = this.currentImageData.width;
const height = this.currentImageData.height;
const tempData = new Uint8ClampedArray(data);
// 计算推拉方向
const deltaX = this.currentMouseX - this.lastMouseX;
const deltaY = this.currentMouseY - this.lastMouseY;
const dragLength = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
this.lastMouseX = this.currentMouseX;
this.lastMouseY = this.currentMouseY;
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));
if (dragLength === 0) {
// 如果没有拖拽,在持续按压时执行基础的外推效果
if (this.isHolding) {
const timeFactor = Math.min(this.pressDuration / 1000, 2.0);
const pushStrength = strength * timeFactor * 0.3;
for (let y = minY; y < maxY; y++) {
for (let x = minX; x < maxX; x++) {
// 此处循环4万次
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < processRadius && distance > 0.1) {
const normalizedDistance = distance / processRadius;
const falloff = 1 - normalizedDistance * normalizedDistance;
const factor = falloff * pushStrength;
// 径向外推效果
const pushX = (dx / distance) * factor;
const pushY = (dy / distance) * factor;
const sourceX = x - pushX;
const sourceY = y - pushY;
const color = this._bicubicInterpolate(tempData, width, height, sourceX, sourceY);
if (color) {
const targetIdx = (y * width + x) * 4;
data[targetIdx] = color[0];
data[targetIdx + 1] = color[1];
data[targetIdx + 2] = color[2];
data[targetIdx + 3] = color[3];
}
}
}
}
}
return true;
}
// 有拖拽时的推拉效果
const dirX = deltaX / dragLength;
const dirY = deltaY / dragLength;
for (let y = minY; y < maxY; y++) {
for (let x = minX; x < maxX; x++) {
// 此处循环4万次
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < processRadius && distance > 0.1) {
const normalizedDistance = distance / processRadius;
const falloff = 1 - normalizedDistance * normalizedDistance;
const factor = falloff * strength;
const offsetX = dirX * factor * Math.min(dragLength * 2, 30);
const offsetY = dirY * factor * Math.min(dragLength * 2, 30);
const sourceX = x - offsetX;
const sourceY = y - offsetY;
const color = this._bicubicInterpolate(tempData, width, height, sourceX, sourceY);
if (color) {
const targetIdx = (y * width + x) * 4;
data[targetIdx] = color[0];
data[targetIdx + 1] = color[1];
data[targetIdx + 2] = color[2];
data[targetIdx + 3] = color[3];
}
}
}
}
return true;
}
/**
* 优化的持续变形效果处理 - 使用增强算法
*/
applyContinuousDeformation() {
if (!this.isHolding || !this.initialized || !this.currentImageData) return;
const { size, pressure, power } = this.params;
const mode = this.currentMode;
const radius = size;
const x = this.initialMouseX;
const y = this.initialMouseY;
const strength = pressure * power;
// 根据模式使用相应的增强算法
switch (mode) {
case this.modes.CLOCKWISE:
this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
break;
case this.modes.COUNTERCLOCKWISE:
this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
break;
case this.modes.PINCH:
this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
break;
case this.modes.EXPAND:
this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
break;
case this.modes.PUSH:
// this._applyEnhancedPushDeformation(x, y, radius, strength);
break;
default: {
// 对于其他模式,使用原有的网格算法
if (!this.mesh) return;
const baseStrength = (pressure * power * this.config.maxStrength) / 100;
const timeFactor = Math.min(this.pressDuration / 1000, 4.0);
const finalStrength = baseStrength * (1.0 + timeFactor * 0.5);
this._applyDeformation(x, y, radius, finalStrength, mode, this.params.distortion);
if (this.config.smoothingIterations > 0) {
this._lightSmoothing();
}
return this._applyMeshToImage();
}
}
// 对于像素算法,直接返回当前图像数据
return this.currentImageData;
}
/**
* 双线性插值函数
* @param {Uint8ClampedArray} data 图像数据
* @param {number} width 图像宽度
* @param {number} height 图像高度
* @param {number} x X坐标
* @param {number} y Y坐标
* @returns {Array|null} RGBA颜色值数组或null
*/
_bilinearInterpolate(data, width, height, x, y) {
const x1 = Math.floor(x);
const y1 = Math.floor(y);
const x2 = Math.min(width - 1, x1 + 1);
const y2 = Math.min(height - 1, y1 + 1);
const dx = x - x1;
const dy = y - y1;
const dx1 = 1 - dx;
const dy1 = 1 - dy;
const index1 = (y1 * width + x1) * 4;
const index2 = (y1 * width + x2) * 4;
const index3 = (y2 * width + x1) * 4;
const index4 = (y2 * width + x2) * 4;
const r =
data[index1] * dx1 * dy1 +
data[index2] * dx * dy1 +
data[index3] * dx1 * dy +
data[index4] * dx * dy;
const g =
data[index1 + 1] * dx1 * dy1 +
data[index2 + 1] * dx * dy1 +
data[index3 + 1] * dx1 * dy +
data[index4 + 1] * dx * dy;
const b =
data[index1 + 2] * dx1 * dy1 +
data[index2 + 2] * dx * dy1 +
data[index3 + 2] * dx1 * dy +
data[index4 + 2] * dx * dy;
const a =
data[index1 + 3] * dx1 * dy1 +
data[index2 + 3] * dx * dy1 +
data[index3 + 3] * dx1 * dy +
data[index4 + 3] * dx * dy;
return [Math.round(r), Math.round(g), Math.round(b), Math.round(a)];
}
/**
* 三次插值实现 - 确保正确处理Alpha通道
* @param {Uint8ClampedArray} data 图像数据
* @param {number} width 图像宽度
* @param {number} height 图像高度
* @param {number} x X坐标
* @param {number} y Y坐标
* @returns {Array|null} RGBA颜色值数组或null
*/
_bicubicInterpolate(data, width, height, x, y) {
// return this._bilinearInterpolate(data, width, height, x, y);
// 获取周围16个像素点
const x1 = Math.floor(x) - 1;
const y1 = Math.floor(y) - 1;
// 创建16个采样点的颜色数组
const pixels = [];
for (let ky = 0; ky < 4; ky++) {
for (let kx = 0; kx < 4; kx++) {
const px = Math.max(0, Math.min(width - 1, x1 + kx));
const py = Math.max(0, Math.min(height - 1, y1 + ky));
const idx = (py * width + px) * 4;
pixels[ky * 4 + kx] = [data[idx], data[idx + 1], data[idx + 2], data[idx + 3]];
}
}
// 计算小数部分
const fx = x - (x1 + 1);
const fy = y - (y1 + 1);
// 计算行插值
const row0 = this._cubicInterpolateRow(pixels[0], pixels[1], pixels[2], pixels[3], fx);
const row1 = this._cubicInterpolateRow(pixels[4], pixels[5], pixels[6], pixels[7], fx);
const row2 = this._cubicInterpolateRow(pixels[8], pixels[9], pixels[10], pixels[11], fx);
const row3 = this._cubicInterpolateRow(pixels[12], pixels[13], pixels[14], pixels[15], fx);
// 计算最终结果
return this._cubicInterpolateRow(row0, row1, row2, row3, fy);
}
// 三次插值辅助方法 - 单行插值
_cubicInterpolateRow(p0, p1, p2, p3, t) {
// 使用三次多项式插值公式
const a = [0, 0, 0, 0];
const b = [0, 0, 0, 0];
const c = [0, 0, 0, 0];
// 为每个通道计算插值系数
for (let i = 0; i < 4; i++) {
a[i] = -0.5 * p0[i] + 1.5 * p1[i] - 1.5 * p2[i] + 0.5 * p3[i];
b[i] = p0[i] - 2.5 * p1[i] + 2 * p2[i] - 0.5 * p3[i];
c[i] = -0.5 * p0[i] + 0.5 * p2[i];
}
// 应用三次多项式
const t2 = t * t;
const t3 = t * t2;
return [
Math.round(a[0] * t3 + b[0] * t2 + c[0] * t + p1[0]),
Math.round(a[1] * t3 + b[1] * t2 + c[1] * t + p1[1]),
Math.round(a[2] * t3 + b[2] * t2 + c[2] * t + p1[2]),
Math.round(a[3] * t3 + b[3] * t2 + c[3] * t + p1[3]) // 确保Alpha通道也被正确插值
];
}
/**
* 应用变形到网格 - 原有的网格算法(用于其他模式)
*/
_applyDeformation(x, y, radius, strength, mode, distortion) {
if (!this.mesh) return;
const points = this.mesh.deformedPoints;
const originalPoints = this.mesh.originalPoints;
// 性能优化:只计算影响范围内的网格点
const affectedPoints = this._getAffectedPoints(x, y, radius);
for (const pointInfo of affectedPoints) {
const { index: i, point, originalPoint, distance } = pointInfo;
if (distance > 0) {
// 使用优化的衰减函数
const normalizedDistance = distance / radius;
const factor = this._optimizedFalloff(normalizedDistance) * strength;
switch (mode) {
case this.modes.CRYSTAL: {
// 水晶模式
const dx = point.x - x;
const dy = point.y - y;
const crystalAngle = Math.atan2(dy, dx);
const crystalRadius = normalizedDistance;
const baseDistortion = Math.max(distortion, 0.3);
const timeFactor = Math.min(this.pressDuration / 1000, 2.0);
const timeEnhancedDistortion = baseDistortion * (1.0 + timeFactor * 0.3);
const wave1 = Math.sin(crystalAngle * 8 + this.pressDuration * 0.005) * 0.6;
const wave2 = Math.cos(crystalAngle * 12 + this.pressDuration * 0.003) * 0.4;
const waveAngle = crystalAngle + (wave1 + wave2) * timeEnhancedDistortion;
const radialMod =
1 + Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) * 0.3;
const modDistance = distance * radialMod;
const crystalX = x + Math.cos(waveAngle) * modDistance;
const crystalY = y + Math.sin(waveAngle) * modDistance;
const crystalFactor = factor * timeEnhancedDistortion * 0.7;
point.x += (crystalX - point.x) * crystalFactor;
point.y += (crystalY - point.y) * crystalFactor;
break;
}
case this.modes.EDGE: {
// 边缘模式
const dx = point.x - x;
const dy = point.y - y;
const edgeAngle = Math.atan2(dy, dx);
const edgeRadius = normalizedDistance;
const baseEdgeDistortion = Math.max(distortion, 0.5);
const timeFactor = Math.min(this.pressDuration / 1000, 2.5);
const timeEnhancedDistortion = baseEdgeDistortion * (1.0 + timeFactor * 0.4);
const edgeWave =
Math.sin(edgeRadius * Math.PI * 4 + this.pressDuration * 0.004) *
Math.cos(edgeAngle * 6 + this.pressDuration * 0.002);
const perpAngle = edgeAngle + Math.PI / 2;
const edgeFactor = edgeWave * factor * timeEnhancedDistortion * 0.5;
const edgeOffsetX = Math.cos(perpAngle) * edgeFactor;
const edgeOffsetY = Math.sin(perpAngle) * edgeFactor;
point.x += edgeOffsetX;
point.y += edgeOffsetY;
break;
}
case this.modes.RECONSTRUCT: {
// 重建模式
const restoreFactor = factor * 0.2;
point.x += (originalPoint.x - point.x) * restoreFactor;
point.y += (originalPoint.y - point.y) * restoreFactor;
break;
}
}
}
}
}
/**
* 获取受影响的网格点(范围优化)
*/
_getAffectedPoints(centerX, centerY, radius) {
const { cols, rows, gridSize } = this.mesh;
const points = this.mesh.deformedPoints;
const originalPoints = this.mesh.originalPoints;
const affectedPoints = [];
// 计算影响范围的网格边界
const minGridX = Math.max(0, Math.floor((centerX - radius) / gridSize));
const maxGridX = Math.min(cols, Math.ceil((centerX + radius) / gridSize));
const minGridY = Math.max(0, Math.floor((centerY - radius) / gridSize));
const maxGridY = Math.min(rows, Math.ceil((centerY + radius) / gridSize));
// 只遍历影响范围内的网格点
for (let gridY = minGridY; gridY <= maxGridY; gridY++) {
for (let gridX = minGridX; gridX <= maxGridX; gridX++) {
const index = gridY * (cols + 1) + gridX;
if (index < points.length) {
const point = points[index];
const originalPoint = originalPoints[index];
const dx = point.x - centerX;
const dy = point.y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
// 只包含在影响半径内的点
if (distance <= radius) {
affectedPoints.push({
index,
point,
originalPoint,
distance,
dx,
dy,
});
}
}
}
}
return affectedPoints;
}
_smoothMesh() {
const { rows, cols } = this.mesh;
const points = this.mesh.deformedPoints;
const tempPoints = points.map((p) => ({ x: p.x, y: p.y }));
for (let iteration = 0; iteration < this.config.smoothingIterations; iteration++) {
for (let y = 1; y < rows; y++) {
for (let x = 1; x < cols; x++) {
const idx = y * (cols + 1) + x;
const left = points[y * (cols + 1) + (x - 1)];
const right = points[y * (cols + 1) + (x + 1)];
const top = points[(y - 1) * (cols + 1) + x];
const bottom = points[(y + 1) * (cols + 1) + x];
const centerX = (left.x + right.x + top.x + bottom.x) / 4;
const centerY = (left.y + right.y + top.y + bottom.y) / 4;
const relaxFactor = this.config.relaxFactor;
tempPoints[idx].x += (centerX - points[idx].x) * relaxFactor;
tempPoints[idx].y += (centerY - points[idx].y) * relaxFactor;
}
}
for (let i = 0; i < points.length; i++) {
points[i].x = tempPoints[i].x;
points[i].y = tempPoints[i].y;
}
}
}
/**
* 专门为旋转模式优化的网格平滑
*/
_lightSmoothing() {
const { rows, cols } = this.mesh;
const points = this.mesh.deformedPoints;
const tempPoints = points.map((p) => ({ x: p.x, y: p.y }));
// 只进行一次轻微平滑
for (let y = 1; y < rows; y++) {
for (let x = 1; x < cols; x++) {
const idx = y * (cols + 1) + x;
const left = points[y * (cols + 1) + (x - 1)];
const right = points[y * (cols + 1) + (x + 1)];
const top = points[(y - 1) * (cols + 1) + x];
const bottom = points[(y + 1) * (cols + 1) + x];
const centerX = (left.x + right.x + top.x + bottom.x) / 4;
const centerY = (left.y + right.y + top.y + bottom.y) / 4;
// 使用更小的松弛因子
const lightRelaxFactor = this.config.relaxFactor * 0.3;
tempPoints[idx].x += (centerX - points[idx].x) * lightRelaxFactor;
tempPoints[idx].y += (centerY - points[idx].y) * lightRelaxFactor;
}
}
for (let i = 0; i < points.length; i++) {
points[i].x = tempPoints[i].x;
points[i].y = tempPoints[i].y;
}
}
/**
* 使用更优化的衰减函数
* @param {number} t 归一化距离 (0-1)
* @returns {number} 衰减因子 (0-1)
*/
_optimizedFalloff(t) {
if (t >= 1.0) return 0;
// 对于旋转模式,使用专门的衰减函数
if (
this.currentMode === this.modes.CLOCKWISE ||
this.currentMode === this.modes.COUNTERCLOCKWISE
) {
return this._stableRotationFalloff(t); // 修复函数名
}
// 其他模式使用原来的衰减函数
const smoothT = 1 - t;
// 多项式衰减 + 指数衰减的组合
const polynomial = smoothT * smoothT * (3 - 2 * smoothT); // 平滑阶梯函数
const exponential = Math.exp(-t * 2); // 指数衰减
// 组合两种衰减方式,在不同区域有不同特性
const weight = Math.cos(t * Math.PI * 0.5); // 权重函数
return polynomial * weight + exponential * (1 - weight);
}
_applyMeshToImage() {
if (!this.mesh || !this.originalImageData) {
return this.currentImageData;
}
const width = this.originalImageData.width;
const height = this.originalImageData.height;
const result = new ImageData(width, height);
const srcData = this.originalImageData.data;
const dstData = result.data;
// 移除步长采样始终使用1:1采样
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const srcPos = this._mapPointBack(x, y);
const dstIdx = (y * width + x) * 4;
if (srcPos.x >= 0 && srcPos.x < width && srcPos.y >= 0 && srcPos.y < height) {
// 使用双三次插值获取颜色
const color = this._bicubicInterpolate(srcData, width, height, srcPos.x, srcPos.y);
dstData[dstIdx] = color[0];
dstData[dstIdx + 1] = color[1];
dstData[dstIdx + 2] = color[2];
dstData[dstIdx + 3] = color[3]; // 确保Alpha通道值被正确设置
} else {
// 对于边界外的点使用最近的有效像素或保持原Alpha通道
// 这里我们确保Alpha通道不为0防止出现透明区域
const nearestX = Math.max(0, Math.min(width - 1, Math.round(srcPos.x)));
const nearestY = Math.max(0, Math.min(height - 1, Math.round(srcPos.y)));
const nearestIdx = (nearestY * width + nearestX) * 4;
// 复制最近像素的颜色但保持Alpha通道为不透明
dstData[dstIdx] = srcData[nearestIdx];
dstData[dstIdx + 1] = srcData[nearestIdx + 1];
dstData[dstIdx + 2] = srcData[nearestIdx + 2];
dstData[dstIdx + 3] = 255; // 强制设置为完全不透明
}
}
}
this.currentImageData = result;
// 添加锐化处理
if (this.config.sharpenAmount > 0) {
this.currentImageData = this._sharpenImage(this.currentImageData, this.config.sharpenAmount);
}
return result;
}
// 添加异步处理方法用于大图像
async applyDeformationAsync(x, y) {
return new Promise((resolve) => {
setTimeout(() => {
const result = this.applyDeformation(x, y);
resolve(result);
}, 0);
});
}
// 批量处理方法
applyDeformationBatch(positions) {
if (!this.initialized || !this.mesh || positions.length === 0) {
return this.currentImageData;
}
// 对于批量处理,模拟连续的拖拽操作
if (positions.length > 0) {
// 使用第一个位置作为初始点
this.startDeformation(positions[0].x, positions[0].y);
// 逐个应用每个位置的变形
positions.forEach((pos, index) => {
if (index === 0) return; // 跳过第一个,因为已经作为初始点
// 更新当前位置并应用变形
this.currentMouseX = pos.x;
this.currentMouseY = pos.y;
// 重新计算拖拽参数
const deltaX = this.currentMouseX - this.initialMouseX;
const deltaY = this.currentMouseY - this.initialMouseY;
this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
this.dragAngle = Math.atan2(deltaY, deltaX);
const { size, pressure, distortion, power } = this.params;
const mode = this.currentMode;
const radius = size * 0.8;
// 根据推拉模式和拖拽距离动态调整强度
let strength;
if (mode === this.modes.PUSH) {
const baseStrength = (pressure * power * this.config.maxStrength) / 100;
const distanceFactor = Math.min(this.dragDistance / radius, 2.0);
strength = baseStrength * distanceFactor * 0.3; // 批量处理时降低强度
} else {
strength = (pressure * power * this.config.maxStrength) / 100;
}
this._applyDeformation(pos.x, pos.y, radius, strength, mode, distortion);
});
// 结束拖拽操作
this.endDeformation();
}
if (this.config.smoothingIterations > 0) {
this._smoothMesh();
}
return this._applyMeshToImage();
}
/**
* 改进的网格映射算法 - 防止空白区域
*/
_mapPointBack(x, y) {
const { cols, rows, gridSize } = this.mesh;
const gridX = x / gridSize;
const gridY = y / gridSize;
const x1 = Math.floor(gridX);
const y1 = Math.floor(gridY);
const x2 = Math.min(x1 + 1, cols);
const y2 = Math.min(y1 + 1, rows);
const fx = gridX - x1;
const fy = gridY - y1;
// 获取四个网格点的变形和原始坐标
const deformed = [
this.mesh.deformedPoints[y1 * (cols + 1) + x1],
this.mesh.deformedPoints[y1 * (cols + 1) + x2],
this.mesh.deformedPoints[y2 * (cols + 1) + x1],
this.mesh.deformedPoints[y2 * (cols + 1) + x2],
];
const original = [
this.mesh.originalPoints[y1 * (cols + 1) + x1],
this.mesh.originalPoints[y1 * (cols + 1) + x2],
this.mesh.originalPoints[y2 * (cols + 1) + x1],
this.mesh.originalPoints[y2 * (cols + 1) + x2],
];
// 双线性插值计算变形后的位置
const deformedX =
(1 - fx) * (1 - fy) * deformed[0].x +
fx * (1 - fy) * deformed[1].x +
(1 - fx) * fy * deformed[2].x +
fx * fy * deformed[3].x;
const deformedY =
(1 - fx) * (1 - fy) * deformed[0].y +
fx * (1 - fy) * deformed[1].y +
(1 - fx) * fy * deformed[2].y +
fx * fy * deformed[3].y;
// 计算原始网格位置
const originalX = x1 * gridSize + fx * gridSize;
const originalY = y1 * gridSize + fy * gridSize;
// 检查是否接近边缘,如果是则减少偏移量
const isNearEdge = this._isNearEdge(originalX, originalY);
const edgeProtectionFactor = isNearEdge ? 0.2 : 1.0; // 边缘区域减少变形量
// 计算偏移量并应用反向映射
const offsetX = (deformedX - originalX) * edgeProtectionFactor;
const offsetY = (deformedY - originalY) * edgeProtectionFactor;
return {
x: Math.max(0, Math.min(this.mesh.width - 1, x - offsetX)),
y: Math.max(0, Math.min(this.mesh.height - 1, y - offsetY)),
};
}
// 边缘检测辅助方法
_isNearEdge(x, y, threshold = 10) {
if (!this.originalImageData) return false;
const data = this.originalImageData.data;
const width = this.originalImageData.width;
const height = this.originalImageData.height;
// 检查像素是否在边缘
if (x <= 0 || x >= width - 1 || y <= 0 || y >= height - 1) return true;
// 简单的Sobel边缘检测
const getPixelBrightness = (px, py) => {
const idx = (py * width + px) * 4;
return (data[idx] + data[idx + 1] + data[idx + 2]) / 3;
};
const kernelX = [
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
];
const kernelY = [
[-1, -2, -1],
[0, 0, 0],
[1, 2, 1]
];
let gradientX = 0;
let gradientY = 0;
for (let ky = -1; ky <= 1; ky++) {
for (let kx = -1; kx <= 1; kx++) {
const px = Math.min(Math.max(0, x + kx), width - 1);
const py = Math.min(Math.max(0, y + ky), height - 1);
const brightness = getPixelBrightness(px, py);
gradientX += brightness * kernelX[ky + 1][kx + 1];
gradientY += brightness * kernelY[ky + 1][kx + 1];
}
}
const gradientMagnitude = Math.sqrt(gradientX * gradientX + gradientY * gradientY);
return gradientMagnitude > threshold;
}
// 图像锐化方法
_sharpenImage(imageData, amount = 0.5) {
if (!imageData) return imageData;
const data = new Uint8ClampedArray(imageData.data);
const width = imageData.width;
const height = imageData.height;
const result = new ImageData(width, height);
const dstData = result.data;
// 锐化核 - 中心为5周围为-1
const kernel = [
[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]
];
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// 边缘像素不处理
if (x === 0 || x === width - 1 || y === 0 || y === height - 1) {
const idx = (y * width + x) * 4;
for (let c = 0; c < 4; c++) {
dstData[idx + c] = data[idx + c];
}
continue;
}
const sharpened = [0, 0, 0, 0];
// 应用锐化核
for (let ky = -1; ky <= 1; ky++) {
for (let kx = -1; kx <= 1; kx++) {
const px = x + kx;
const py = y + ky;
const idx = (py * width + px) * 4;
const weight = kernel[ky + 1][kx + 1];
for (let c = 0; c < 3; c++) { // 只锐化RGB通道
sharpened[c] += data[idx + c] * weight;
}
sharpened[3] = data[idx + 3]; // 保持Alpha通道不变
}
}
// 应用锐化强度并裁剪值范围
const idx = (y * width + x) * 4;
for (let c = 0; c < 3; c++) {
const original = data[idx + c];
const diff = sharpened[c] - original;
dstData[idx + c] = Math.max(0, Math.min(255, original + diff * amount));
}
dstData[idx + 3] = sharpened[3];
}
}
return result;
}
_bilinearInterpolate(data, width, height, x, y) {
const x1 = Math.floor(x);
const y1 = Math.floor(y);
const x2 = Math.min(x1 + 1, width - 1);
const y2 = Math.min(y1 + 1, height - 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]
),
];
}
reset() {
if (!this.mesh || !this.originalImageData) return false;
for (let i = 0; i < this.mesh.deformedPoints.length; i++) {
this.mesh.deformedPoints[i].x = this.mesh.originalPoints[i].x;
this.mesh.deformedPoints[i].y = this.mesh.originalPoints[i].y;
}
this.currentImageData = new ImageData(
new Uint8ClampedArray(this.originalImageData.data),
this.originalImageData.width,
this.originalImageData.height
);
// 重置拖拽状态
this.initialMouseX = 0;
this.initialMouseY = 0;
this.currentMouseX = 0;
this.currentMouseY = 0;
this.lastMouseX = 0;
this.lastMouseY = 0;
this.mouseMovementX = 0;
this.mouseMovementY = 0;
this.isFirstApply = true;
this.isDragging = false;
this.dragDistance = 0;
this.dragAngle = 0;
// 新增:重置持续按压状态
this.isHolding = false;
this.pressStartTime = 0;
this.pressDuration = 0;
this.accumulatedRotation = 0;
this.accumulatedScale = 0;
this.lastApplyTime = 0;
this.deformHistory = [];
return true;
}
// 新增:获取持续按压状态信息
getHoldingInfo() {
return {
isHolding: this.isHolding,
pressDuration: this.pressDuration,
accumulatedRotation: this.accumulatedRotation,
accumulatedScale: this.accumulatedScale,
pressStartTime: this.pressStartTime,
};
}
/**
* 应用液化变形 - 主要的公共接口方法
* @param {Number} x X坐标
* @param {Number} y Y坐标
* @returns {ImageData} 变形后的图像数据
*/
applyDeformation(x, y) {
if (!this.initialized || !this.mesh || !this.originalImageData) {
console.warn("液化管理器未初始化或缺少必要数据");
return this.currentImageData;
}
// 更新鼠标位置
this.currentMouseX = x;
this.currentMouseY = y;
// 计算拖拽参数
const deltaX = this.currentMouseX - this.initialMouseX;
const deltaY = this.currentMouseY - this.initialMouseY;
this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
this.dragAngle = Math.atan2(deltaY, deltaX);
// 获取当前参数
const { size, pressure, power } = this.params;
const mode = this.currentMode;
const radius = size;
const strength = pressure * power;
// 根据模式选择算法
const pixelModes = [
this.modes.CLOCKWISE,
this.modes.COUNTERCLOCKWISE,
this.modes.PINCH,
this.modes.EXPAND,
this.modes.PUSH,
];
if (pixelModes.includes(mode)) {
// 使用增强的像素算法
switch (mode) {
case this.modes.CLOCKWISE:
this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
break;
case this.modes.COUNTERCLOCKWISE:
this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
break;
case this.modes.PINCH:
this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
break;
case this.modes.EXPAND:
this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
break;
case this.modes.PUSH:
this._applyEnhancedPushDeformation(x, y, radius, strength);
break;
}
// 更新最后应用时间
this.lastApplyTime = Date.now();
this.isFirstApply = false;
return this.currentImageData;
} else {
// 使用原有的网格算法处理其他模式
if (!this.mesh) {
console.warn("网格未初始化");
return this.currentImageData;
}
const finalStrength = (strength * this.config.maxStrength) / 100;
// 应用变形
this._applyDeformation(x, y, radius, finalStrength, mode, this.params.distortion);
// 有条件地应用平滑处理,仅在特定模式下应用
const smoothingModes = [this.modes.CRYSTAL, this.modes.EDGE];
if (smoothingModes.includes(mode) && this.config.smoothingIterations > 0) {
this._smoothMesh();
}
// 更新图像数据
const result = this._applyMeshToImage();
// 更新最后应用时间
this.lastApplyTime = Date.now();
this.isFirstApply = false;
return result;
}
}
getCurrentImageData() {
return this.currentImageData;
}
destroy() {
// 停止持续效果定时器
this.stopContinuousEffect();
this.originalImageData = null;
this.currentImageData = null;
this.mesh = null;
this.deformHistory = [];
this.initialized = false;
// 清理拖拽状态
this.initialMouseX = 0;
this.initialMouseY = 0;
this.currentMouseX = 0;
this.currentMouseY = 0;
this.lastMouseX = 0;
this.lastMouseY = 0;
this.mouseMovementX = 0;
this.mouseMovementY = 0;
this.isFirstApply = true;
this.isDragging = false;
this.dragDistance = 0;
this.dragAngle = 0;
// 新增:清理持续按压状态
this.isHolding = false;
this.pressStartTime = 0;
this.pressDuration = 0;
this.accumulatedRotation = 0;
this.accumulatedScale = 0;
this.lastApplyTime = 0;
}
/**
* 释放资源 - 别名方法,与其他管理器保持一致
*/
dispose() {
this.destroy();
}
}