import { SpectrumRenderer, buildSpectrumPalette, hslToHex, rgbaFromHex } from './SpectrumRenderer' /** * 圆形频谱渲染器 * 环形排列的放射线,缓慢旋转,彩虹色环效果 */ export class CircleSpectrumRenderer implements SpectrumRenderer { private rotation: number = 0 render(ctx: CanvasRenderingContext2D, bands: number[], energy: number, width: number, height: number, brandColor: string): void { const bandCount: number = bands.length if (bandCount === 0) return ctx.fillStyle = 'rgba(0, 0, 0, 0.20)' ctx.fillRect(0, 0, width, height) const centerX: number = width / 2 const centerY: number = height / 2 const innerRadius: number = Math.min(width, height) * 0.08 const maxOuterRadius: number = Math.min(width, height) * 0.48 const palette = buildSpectrumPalette(brandColor) // 旋转速度随能量变化 (0.5x ~ 1.5x) const rotationSpeed: number = 0.5 + energy * 1.0 this.rotation += rotationSpeed * 0.02 // ~10s 一圈基础速度 if (this.rotation > Math.PI * 2) { this.rotation -= Math.PI * 2 } const angleStep: number = (Math.PI * 2) / bandCount // 中央光晕 const coreGradient: CanvasGradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, maxOuterRadius * 0.7) coreGradient.addColorStop(0, rgbaFromHex(hslToHex(palette.primaryHue, 0.9, 0.55), 0.28 + energy * 0.25)) coreGradient.addColorStop(1, 'rgba(0, 0, 0, 0)') ctx.fillStyle = coreGradient ctx.beginPath() ctx.arc(centerX, centerY, maxOuterRadius * 0.7, 0, Math.PI * 2) ctx.fill() for (let i = 0; i < bandCount; i++) { const angle: number = i * angleStep + this.rotation const lineLength: number = innerRadius + Math.pow(bands[i], 0.85) * (maxOuterRadius - innerRadius) const x1: number = centerX + Math.cos(angle) * innerRadius const y1: number = centerY + Math.sin(angle) * innerRadius const x2: number = centerX + Math.cos(angle) * lineLength const y2: number = centerY + Math.sin(angle) * lineLength // 品牌色驱动的色相映射 const hue: number = (palette.primaryHue + (i / bandCount) * 220) % 360 const lightness: number = 0.4 + bands[i] * 0.3 const lineColor: string = hslToHex(hue, 0.85, lightness) ctx.beginPath() ctx.strokeStyle = rgbaFromHex(lineColor, 0.76) ctx.lineWidth = 1 + bands[i] * 2.6 ctx.moveTo(x1, y1) ctx.lineTo(x2, y2) ctx.stroke() } // 外环脉冲 const pulseRadius: number = innerRadius + (maxOuterRadius - innerRadius) * (0.35 + energy * 0.55) ctx.beginPath() ctx.arc(centerX, centerY, pulseRadius, 0, Math.PI * 2) ctx.strokeStyle = rgbaFromHex(hslToHex(palette.complementaryHue, 0.85, 0.62), 0.35) ctx.lineWidth = 2 ctx.stroke() // 中心圆(能量呼吸) const breathRadius: number = innerRadius * (0.9 + energy * 0.8) ctx.beginPath() ctx.arc(centerX, centerY, breathRadius, 0, Math.PI * 2) ctx.fillStyle = rgbaFromHex(hslToHex(palette.coolHue, 0.7, 0.6), 0.18 + energy * 0.24) ctx.fill() } reset(): void { this.rotation = 0 } }