ink.js

2.4 kB · javascript · 85 lines

1import { CLEAR } from "./frame.js";23const NAMES = ["ground", "panel", "line", "fg", "dim", "red", "orange", "yellow", "green", "mint", "teal", "cyan", "blue", "indigo", "purple", "pink", "brown", "gray"];45// COLOR67const byte = (v) => {8  const r = Math.round(v);9  return r > 255 ? 255 : r > 0 ? r : 0;10};11const clamp = (v) => (v < 0 ? 0 : v > 1 ? 1 : v);1213function color(value, name) {14  if (typeof value === "string" && /^#?([0-9a-f]{6}|[0-9a-f]{8})$/i.test(value)) {15    const hex = value.replace("#", "");16    const n = (i) => parseInt(hex.slice(i, i + 2), 16);17    return Object.freeze([n(0), n(2), n(4), hex.length === 8 ? n(6) : 255]);18  }19  if (value && typeof value === "object" && "r" in value) return Object.freeze([value.r, value.g, value.b, value.a ?? 255]);20  if (value && value.length >= 3) return Object.freeze([value[0], value[1], value[2], value[3] ?? 255]);21  throw new Error(`ink: ${name} is not a color: ${value}`);22}2324// MIXING2526export function mix(a, b, t) {27  const s = clamp(t);28  const lerp = (x, y) => byte(x + (y - x) * s);29  return [lerp(a[0], b[0]), lerp(a[1], b[1]), lerp(a[2], b[2]), lerp(a[3], b[3])];30}3132export function fade(c, alpha) {33  return [c[0], c[1], c[2], byte(255 * clamp(alpha))];34}3536// RAMP3738export class Ramp {39  constructor(stops, ground = CLEAR) {40    this.stops = stops;41    this.ground = ground;42  }4344  at(t) {45    const n = this.stops.length;46    if (n === 0) return this.ground;47    if (n === 1) return this.stops[0];48    const s = clamp(t) * (n - 1);49    const i = Math.min(Math.floor(s) || 0, n - 2);50    return mix(this.stops[i], this.stops[i + 1], s - i);51  }52}5354// INK5556export function ink(palette) {57  const out = {};58  for (const name of NAMES) {59    if (palette[name] === undefined) throw new Error(`ink: the palette has no ${name}`);60    out[name] = color(palette[name], name);61  }62  const { ground, fg, blue, orange, yellow, green, pink, indigo } = out;63  class Bound extends Ramp {64    constructor(stops) {65      super(stops, ground);66    }6768    static heat() {69      return new Bound([ground, blue, yellow, fg]);70    }7172    static fire() {73      return new Bound([ground, orange, yellow, fg]);74    }7576    static diverge() {77      return new Bound([blue, ground, orange]);78    }7980    static tone(a, b) {81      return new Bound([a, b]);82    }83  }84  return Object.freeze({ ...out, inks: Object.freeze([blue, orange, yellow, green, pink, indigo]), mix, fade, Ramp: Bound });85}