/** * 混合液化管理器 - 根据模式智能选择算法 */ 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... }