/** * CPU版本的液化管理器 * 修复版本 - 解决三角形网格失真问题 */ export class LiquifyCPUManager { 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.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.lastMouseX = 0; this.lastMouseY = 0; this.mouseMovementX = 0; this.mouseMovementY = 0; this.isFirstApply = true; // 标记是否是首次应用 } 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 in this.params) { this.params[param] = value; return true; } return false; } getParams() { return { ...this.params }; } resetParams() { this.params = { size: 80, // 增大默认尺寸 pressure: 0.8, // 增大默认压力 distortion: 0, power: 0.8, // 增大默认动力 }; } applyDeformation(x, y) { // 计算鼠标移动方向 if (!this.isFirstApply) { this.mouseMovementX = x - this.lastMouseX; this.mouseMovementY = y - this.lastMouseY; } else { // 首次应用时不计算移动,避免初始变形 this.mouseMovementX = 0; this.mouseMovementY = 0; this.isFirstApply = false; } this.lastMouseX = x; this.lastMouseY = y; // 性能优化:限制更新频率 const now = Date.now(); if (now - this.lastUpdateTime < this.updateThrottle || this.isProcessing) { return this.currentImageData; } this.isProcessing = true; this.lastUpdateTime = now; if (!this.initialized || !this.mesh) { this.isProcessing = false; return this.currentImageData; } const { size, pressure, distortion, power } = this.params; const mode = this.currentMode; const radius = size * 1.2; // 稍微增大影响半径 const strength = (pressure * power * this.config.maxStrength) / 20; // 调整基础强度 this._applyDeformation(x, y, radius, strength, mode, distortion); if (this.config.smoothingIterations > 0) { this._smoothMesh(); } const result = this._applyMeshToImage(); this.isProcessing = false; return result; } _applyDeformation(x, y, radius, strength, mode, distortion) { if (!this.mesh) return; const points = this.mesh.deformedPoints; const originalPoints = this.mesh.originalPoints; for (let i = 0; i < points.length; i++) { const point = points[i]; const originalPoint = originalPoints[i]; const dx = point.x - x; const dy = point.y - y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < radius && distance > 0) { // 使用平方衰减函数 const factor = Math.pow(1 - distance / radius, 2) * strength * 0.1; // 大幅降低基础系数 switch (mode) { case this.modes.PUSH: { // 推拉模式 - 真正的拖拽效果 // 计算实际移动距离 const movementLength = Math.sqrt( this.mouseMovementX * this.mouseMovementX + this.mouseMovementY * this.mouseMovementY ); // 只有在有足够移动距离时才应用效果 if (movementLength > 1.0) { // 提高阈值,确保有明显移动 // 归一化移动方向 const moveX = this.mouseMovementX / movementLength; const moveY = this.mouseMovementY / movementLength; // 计算衰减(距离中心越近,效果越强) const radiusRatio = distance / radius; const falloff = Math.pow(1 - radiusRatio, 2.0); // 使用更强的衰减 // 基于实际移动距离计算强度 const { pressure, power } = this.params; const moveStrength = pressure * power * movementLength * 0.3; // 降低移动强度系数 // 计算最终拖拽强度 const dragStrength = moveStrength * falloff * factor; // 向鼠标移动方向拖拽 const dragX = moveX * dragStrength; const dragY = moveY * dragStrength; // 应用变形,但限制最大变形量 const maxDeform = 2.0; // 限制单次最大变形量 point.x += Math.max(-maxDeform, Math.min(maxDeform, dragX)); point.y += Math.max(-maxDeform, Math.min(maxDeform, dragY)); } break; } case this.modes.CLOCKWISE: case this.modes.COUNTERCLOCKWISE: { // 旋转模式 - 保持原有效果 const angle = Math.atan2(dy, dx); const direction = mode === this.modes.CLOCKWISE ? 1 : -1; const rotationAngle = angle + direction * factor; const newX = x + Math.cos(rotationAngle) * distance; const newY = y + Math.sin(rotationAngle) * distance; point.x += (newX - point.x) * 0.8; point.y += (newY - point.y) * 0.8; break; } case this.modes.PINCH: { // 捏合模式 - 保持原有效果 const pinchStrength = factor * 1.2; point.x -= dx * pinchStrength; point.y -= dy * pinchStrength; break; } case this.modes.EXPAND: { // 展开模式 - 参考捏合的反向操作 const expandFactor = factor * 1.5; point.x += dx * expandFactor; point.y += dy * expandFactor; break; } case this.modes.CRYSTAL: { // 水晶模式 - 参考旋转算法创建多重波形 const crystalAngle = Math.atan2(dy, dx); const crystalRadius = distance / radius; // 确保有基础效果 const baseDistortion = Math.max(distortion, 0.3); // 多重波形 - 类似旋转的角度调制 const wave1 = Math.sin(crystalAngle * 8) * 0.6; const wave2 = Math.cos(crystalAngle * 12) * 0.4; const waveAngle = crystalAngle + (wave1 + wave2) * baseDistortion; // 径向调制 - 类似旋转的距离调制 const radialMod = 1 + Math.sin(crystalRadius * Math.PI * 2) * 0.3; const modDistance = distance * radialMod; const crystalX = x + Math.cos(waveAngle) * modDistance; const crystalY = y + Math.sin(waveAngle) * modDistance; const crystalFactor = factor * baseDistortion; point.x += (crystalX - point.x) * crystalFactor; point.y += (crystalY - point.y) * crystalFactor; break; } case this.modes.EDGE: { // 边缘模式 - 参考旋转算法创建垂直波纹 const edgeAngle = Math.atan2(dy, dx); const edgeRadius = distance / radius; // 确保有基础效果 const baseEdgeDistortion = Math.max(distortion, 0.5); // 边缘波纹 - 垂直于径向的调制 const edgeWave = Math.sin(edgeRadius * Math.PI * 4) * Math.cos(edgeAngle * 6); const perpAngle = edgeAngle + Math.PI / 2; // 垂直角度 const edgeFactor = edgeWave * factor * baseEdgeDistortion; 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.15; point.x += (originalPoint.x - point.x) * restoreFactor; point.y += (originalPoint.y - point.y) * restoreFactor; break; } } } } } // 优化衰减函数,使过渡更平滑 _smoothFalloff(t) { if (t >= 1) return 0; // 使用更平滑的衰减曲线 const smoothT = 1 - t; return smoothT * smoothT * smoothT * (3 - 2 * smoothT); } _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; } } } _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; // 性能优化:使用步长采样减少计算量 const step = width > 1000 || height > 1000 ? 2 : 1; for (let y = 0; y < height; y += step) { for (let x = 0; x < width; x += step) { const srcPos = this._mapPointBack(x, y); if ( srcPos.x >= 0 && srcPos.x < width && srcPos.y >= 0 && srcPos.y < height ) { const color = this._bilinearInterpolate( srcData, width, height, srcPos.x, srcPos.y ); // 如果使用步长采样,需要填充相邻像素 for (let dy = 0; dy < step && y + dy < height; dy++) { for (let dx = 0; dx < step && x + dx < width; dx++) { const dstIdx = ((y + dy) * width + (x + dx)) * 4; dstData[dstIdx] = color[0]; dstData[dstIdx + 1] = color[1]; dstData[dstIdx + 2] = color[2]; dstData[dstIdx + 3] = color[3]; } } } } } this.currentImageData = result; 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; } const { size, pressure, distortion, power } = this.params; const mode = this.currentMode; const radius = size * 1.0; const strength = (pressure * power * this.config.maxStrength) / 60; // 批量应用所有变形 positions.forEach((pos) => { this._applyDeformation( pos.x, pos.y, radius * 0.5, strength * 0.3, mode, distortion ); }); 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 offsetX = deformedX - originalX; const offsetY = deformedY - originalY; return { x: x - offsetX, y: y - offsetY, }; } _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.deformHistory = []; return true; } getCurrentImageData() { return this.currentImageData; } destroy() { this.originalImageData = null; this.currentImageData = null; this.mesh = null; this.deformHistory = []; this.initialized = false; } }