/** * 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(); } }