import { SpectrumRenderer, SpectrumRenderOptions, buildSpectrumPalette, hslToHex, rgbaFromHex } from './SpectrumRenderer' /** * 点阵频谱渲染器 * 使用点阵网格表达频谱能量层级 */ export class DotMatrixSpectrumRenderer implements SpectrumRenderer { render(ctx: CanvasRenderingContext2D, bands: number[], energy: number, width: number, height: number, brandColor: string, options?: SpectrumRenderOptions): void { if (bands.length === 0) return ctx.clearRect(0, 0, width, height) const palette = buildSpectrumPalette(brandColor) const complexityScale: number = Math.max(0.45, Math.min(1, options?.complexityScale ?? 1)) const columns: number = Math.max(12, Math.floor(20 * complexityScale)) const rows: number = Math.max(8, Math.floor(14 * complexityScale)) const cellW: number = width / columns const cellH: number = height / rows const radius: number = Math.max(1.6, Math.min(cellW, cellH) * 0.28) for (let c = 0; c < columns; c++) { const bandIndex: number = Math.floor((c / columns) * bands.length) const value: number = Math.max(0, Math.min(1, bands[bandIndex])) const activeRows: number = Math.floor(value * rows) const hue: number = (palette.primaryHue + (c / columns) * 135) % 360 for (let r = 0; r < rows; r++) { const lit: boolean = r >= rows - activeRows const x: number = c * cellW + cellW * 0.5 const y: number = r * cellH + cellH * 0.5 const alpha: number = lit ? (0.30 + value * 0.65) : 0.08 const lightness: number = lit ? (0.45 + 0.28 * (r / rows)) : 0.22 ctx.fillStyle = rgbaFromHex(hslToHex(hue, 0.82, lightness), alpha) ctx.beginPath() ctx.arc(x, y, radius, 0, Math.PI * 2) ctx.fill() } } ctx.fillStyle = rgbaFromHex(hslToHex(palette.accentHue, 0.88, 0.60), 0.10 + energy * 0.18) ctx.fillRect(0, height * 0.48, width, 2) } reset(): void { // 无状态 } }