/** Colormaps as 256-entry RGBA lookup tables, built from control points. */ function lut(stops: string[]): Uint8Array { const rgb = stops.map((h) => [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16))); const out = new Uint8Array(256 * 4); for (let i = 0; i < 256; i++) { const t = (i / 255) * (rgb.length - 1); const j = Math.min(rgb.length - 2, Math.floor(t)); const f = t - j; for (let c = 0; c < 3; c++) out[4 * i + c] = Math.round(rgb[j][c] * (1 - f) + rgb[j + 1][c] * f); out[4 * i + 3] = 255; } return out; } /** ColorBrewer RdBu, reversed: blue for negative pressure, red for positive. */ export const DIVERGING = lut(['#053061', '#2166ac', '#4393c3', '#92c5de', '#d1e5f0', '#f7f7f7', '#fddbc7', '#f4a582', '#d6604d', '#b2182b', '#67001f']); /** Viridis, for the level in dB. */ export const SEQUENTIAL = lut(['#440154', '#482878', '#3e4989', '#31688e', '#26828e', '#1f9e89', '#35b779', '#6ece58', '#b5de2b', '#fde725']); /** A vertical colorbar into a canvas. */ export function drawColorbar(canvas: HTMLCanvasElement, map: Uint8Array) { const ctx = canvas.getContext('2d')!; const img = ctx.createImageData(1, 256); for (let i = 0; i < 256; i++) img.data.set(map.subarray(4 * (255 - i), 4 * (255 - i) + 4), 4 * i); const tmp = document.createElement('canvas'); tmp.width = 1; tmp.height = 256; tmp.getContext('2d')!.putImageData(img, 0, 0); ctx.imageSmoothingEnabled = true; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(tmp, 0, 0, canvas.width, canvas.height); }