| 21 | } |
| 22 | |
| 23 | function buildScaleTable(stops: readonly ScaleStop[]): readonly Rgb24[] { |
| 24 | const table: Rgb24[] = []; |
| 25 | for (let index = 0; index < 256; index++) { |
| 26 | const t = index / 255; |
| 27 | let left = stops[0] ?? { t: 0, rgb: rgb(0, 0, 0) }; |
| 28 | let right = stops[stops.length - 1] ?? left; |
| 29 | for (let stopIndex = 0; stopIndex < stops.length - 1; stopIndex++) { |
| 30 | const a = stops[stopIndex]; |
| 31 | const b = stops[stopIndex + 1]; |
| 32 | if (!a || !b) continue; |
| 33 | if (t < a.t || t > b.t) continue; |
| 34 | left = a; |
| 35 | right = b; |
| 36 | break; |
| 37 | } |
| 38 | const span = Math.max(1e-9, right.t - left.t); |
| 39 | const localT = clamp01((t - left.t) / span); |
| 40 | table.push( |
| 41 | rgb( |
| 42 | Math.round(lerp(rgbR(left.rgb), rgbR(right.rgb), localT)), |
| 43 | Math.round(lerp(rgbG(left.rgb), rgbG(right.rgb), localT)), |
| 44 | Math.round(lerp(rgbB(left.rgb), rgbB(right.rgb), localT)), |
| 45 | ), |
| 46 | ); |
| 47 | } |
| 48 | for (const stop of stops) { |
| 49 | const stopIndex = Math.round(clamp01(stop.t) * 255); |
| 50 | table[stopIndex] = stop.rgb; |
| 51 | } |
| 52 | return Object.freeze(table); |
| 53 | } |
| 54 | |
| 55 | const SCALE_TABLES: Readonly<Record<HeatmapColorScale, readonly Rgb24[]>> = Object.freeze({ |
| 56 | viridis: buildScaleTable(HEATMAP_SCALE_ANCHORS.viridis), |