CircleSpectrumRenderer.ets 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import { SpectrumRenderer, buildSpectrumPalette, hslToHex, rgbaFromHex } from './SpectrumRenderer'
  2. /**
  3. * 圆形频谱渲染器
  4. * 环形排列的放射线,缓慢旋转,彩虹色环效果
  5. */
  6. export class CircleSpectrumRenderer implements SpectrumRenderer {
  7. private rotation: number = 0
  8. render(ctx: CanvasRenderingContext2D, bands: number[], energy: number,
  9. width: number, height: number, brandColor: string): void {
  10. const bandCount: number = bands.length
  11. if (bandCount === 0) return
  12. ctx.fillStyle = 'rgba(0, 0, 0, 0.20)'
  13. ctx.fillRect(0, 0, width, height)
  14. const centerX: number = width / 2
  15. const centerY: number = height / 2
  16. const innerRadius: number = Math.min(width, height) * 0.08
  17. const maxOuterRadius: number = Math.min(width, height) * 0.48
  18. const palette = buildSpectrumPalette(brandColor)
  19. // 旋转速度随能量变化 (0.5x ~ 1.5x)
  20. const rotationSpeed: number = 0.5 + energy * 1.0
  21. this.rotation += rotationSpeed * 0.02 // ~10s 一圈基础速度
  22. if (this.rotation > Math.PI * 2) {
  23. this.rotation -= Math.PI * 2
  24. }
  25. const angleStep: number = (Math.PI * 2) / bandCount
  26. // 中央光晕
  27. const coreGradient: CanvasGradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, maxOuterRadius * 0.7)
  28. coreGradient.addColorStop(0, rgbaFromHex(hslToHex(palette.primaryHue, 0.9, 0.55), 0.28 + energy * 0.25))
  29. coreGradient.addColorStop(1, 'rgba(0, 0, 0, 0)')
  30. ctx.fillStyle = coreGradient
  31. ctx.beginPath()
  32. ctx.arc(centerX, centerY, maxOuterRadius * 0.7, 0, Math.PI * 2)
  33. ctx.fill()
  34. for (let i = 0; i < bandCount; i++) {
  35. const angle: number = i * angleStep + this.rotation
  36. const lineLength: number = innerRadius + Math.pow(bands[i], 0.85) * (maxOuterRadius - innerRadius)
  37. const x1: number = centerX + Math.cos(angle) * innerRadius
  38. const y1: number = centerY + Math.sin(angle) * innerRadius
  39. const x2: number = centerX + Math.cos(angle) * lineLength
  40. const y2: number = centerY + Math.sin(angle) * lineLength
  41. // 品牌色驱动的色相映射
  42. const hue: number = (palette.primaryHue + (i / bandCount) * 220) % 360
  43. const lightness: number = 0.4 + bands[i] * 0.3
  44. const lineColor: string = hslToHex(hue, 0.85, lightness)
  45. ctx.beginPath()
  46. ctx.strokeStyle = rgbaFromHex(lineColor, 0.76)
  47. ctx.lineWidth = 1 + bands[i] * 2.6
  48. ctx.moveTo(x1, y1)
  49. ctx.lineTo(x2, y2)
  50. ctx.stroke()
  51. }
  52. // 外环脉冲
  53. const pulseRadius: number = innerRadius + (maxOuterRadius - innerRadius) * (0.35 + energy * 0.55)
  54. ctx.beginPath()
  55. ctx.arc(centerX, centerY, pulseRadius, 0, Math.PI * 2)
  56. ctx.strokeStyle = rgbaFromHex(hslToHex(palette.complementaryHue, 0.85, 0.62), 0.35)
  57. ctx.lineWidth = 2
  58. ctx.stroke()
  59. // 中心圆(能量呼吸)
  60. const breathRadius: number = innerRadius * (0.9 + energy * 0.8)
  61. ctx.beginPath()
  62. ctx.arc(centerX, centerY, breathRadius, 0, Math.PI * 2)
  63. ctx.fillStyle = rgbaFromHex(hslToHex(palette.coolHue, 0.7, 0.6), 0.18 + energy * 0.24)
  64. ctx.fill()
  65. }
  66. reset(): void {
  67. this.rotation = 0
  68. }
  69. }