| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- /**
- * 频谱渲染器接口
- * 所有频谱特效均需实现此接口
- */
- export interface SpectrumRenderer {
- /**
- * 渲染一帧频谱动画
- * @param ctx Canvas 2D 渲染上下文
- * @param bands 频段幅值数组 (0.0 ~ 1.0)
- * @param energy 总体能量值 (0.0 ~ 1.0)
- * @param width Canvas 宽度
- * @param height Canvas 高度
- * @param brandColor 主题品牌色 (hex string, e.g. '#FF0050')
- */
- render(ctx: CanvasRenderingContext2D, bands: number[], energy: number,
- width: number, height: number, brandColor: string, options?: SpectrumRenderOptions): void
- /**
- * 重置渲染器状态
- */
- reset(): void
- }
- export interface SpectrumRenderOptions {
- complexityScale: number
- }
- export interface SpectrumPalette {
- primaryHue: number
- warmHue: number
- coolHue: number
- complementaryHue: number
- accentHue: number
- }
- /**
- * 规范化 hex 颜色字符串,异常时回退到默认值
- */
- export function normalizeHexColor(color: string, fallback: string = '#FF0050'): string {
- if (!color || color.length < 7 || color[0] !== '#') {
- return fallback
- }
- const hexBody: string = color.substring(1, 7)
- const valid: boolean = /^[0-9a-fA-F]{6}$/.test(hexBody)
- return valid ? '#' + hexBody : fallback
- }
- /**
- * 基于品牌色构建频谱调色板(HSL 色相偏移)
- */
- export function buildSpectrumPalette(brandColor: string): SpectrumPalette {
- const baseColor: string = normalizeHexColor(brandColor)
- const baseHue: number = hexToHue(baseColor)
- return {
- primaryHue: baseHue,
- warmHue: (baseHue - 28 + 360) % 360,
- coolHue: (baseHue + 28) % 360,
- complementaryHue: (baseHue + 180) % 360,
- accentHue: (baseHue + 55) % 360
- }
- }
- /**
- * 生成 rgba 字符串,便于 Canvas 透明度控制
- */
- export function rgbaFromHex(hex: string, alpha: number): string {
- const safeHex: string = normalizeHexColor(hex)
- const safeAlpha: number = Math.max(0, Math.min(1, alpha))
- const r: number = parseInt(safeHex.substring(1, 3), 16)
- const g: number = parseInt(safeHex.substring(3, 5), 16)
- const b: number = parseInt(safeHex.substring(5, 7), 16)
- return `rgba(${r}, ${g}, ${b}, ${safeAlpha})`
- }
- /**
- * HSL 转 RGB hex 字符串
- */
- export function hslToHex(h: number, s: number, l: number): string {
- const hNorm: number = ((h % 360) + 360) % 360
- const sNorm: number = Math.max(0, Math.min(1, s))
- const lNorm: number = Math.max(0, Math.min(1, l))
- const c: number = (1 - Math.abs(2 * lNorm - 1)) * sNorm
- const x: number = c * (1 - Math.abs((hNorm / 60) % 2 - 1))
- const m: number = lNorm - c / 2
- let r: number = 0
- let g: number = 0
- let b: number = 0
- if (hNorm < 60) { r = c; g = x; b = 0 }
- else if (hNorm < 120) { r = x; g = c; b = 0 }
- else if (hNorm < 180) { r = 0; g = c; b = x }
- else if (hNorm < 240) { r = 0; g = x; b = c }
- else if (hNorm < 300) { r = x; g = 0; b = c }
- else { r = c; g = 0; b = x }
- const ri: number = Math.round((r + m) * 255)
- const gi: number = Math.round((g + m) * 255)
- const bi: number = Math.round((b + m) * 255)
- const toHex = (v: number): string => {
- const hex: string = v.toString(16)
- return hex.length < 2 ? '0' + hex : hex
- }
- return '#' + toHex(ri) + toHex(gi) + toHex(bi)
- }
- /**
- * 从 hex 字符串解析 HSL 色相
- */
- export function hexToHue(hex: string): number {
- let r: number = 0
- let g: number = 0
- let b: number = 0
- if (hex.length >= 7) {
- r = parseInt(hex.substring(1, 3), 16) / 255
- g = parseInt(hex.substring(3, 5), 16) / 255
- b = parseInt(hex.substring(5, 7), 16) / 255
- }
- const max: number = Math.max(r, g, b)
- const min: number = Math.min(r, g, b)
- const d: number = max - min
- let h: number = 0
- if (d !== 0) {
- if (max === r) h = ((g - b) / d) % 6
- else if (max === g) h = (b - r) / d + 2
- else h = (r - g) / d + 4
- h *= 60
- if (h < 0) h += 360
- }
- return h
- }
|