import * as R from '../rand.js'; import * as Color from './def.js'; const max = Math.max; const min = Math.min; export type State = R.State; export type CloseFar = 'close' | 'far'; export type LightDark = 'light' | 'dark'; export class Rand extends R.Rand { maxl: Color.Luma = 0.9; minl: Color.Luma = 0.4; minlLight: Color.Luma = 0.7; maxlDark: Color.Luma = 0.65; mincLight: Color.Chroma = 0.08; maxcLight: Color.Chroma = 0.1; mincDark: Color.Chroma = 0.12; maxcDark: Color.Chroma = 0.175; // max spread for a sequence of analogous colors. unless that would put them // too close together maxhWidth: Color.HueDistance = 80; // minimum distance between adjacent analogous colors minhSep: Color.HueDistance = 5; // size of the wedge a "complementary" color can be in maxhCompl: Color.HueDistance = 40; // size of the wedge a "triadic" color can be in maxhTriad: Color.HueDistance = 25; constructor(); constructor([a, b, c, d]: State); constructor(str: string); constructor(st?: State | string) { if (st === undefined) super(); else if (typeof st === 'string') super(st); else super(st); } isLight(l: Color.Luma): boolean { return l >= this.minlLight; } lightFor(baseL: Color.Luma, d: CloseFar = 'close'): Color.Luma { const maxl = d == 'close' ? min(this.maxl, baseL * 1.25) : this.maxl; return this.float(baseL, maxl); } darkFor(baseL: Color.Luma, d: CloseFar = 'close'): Color.Luma { const minl = d == 'close' ? max(this.minl, baseL * 0.8) : this.minl return this.float(minl, baseL); } brightFor(l: Color.Luma, baseC: Color.Chroma): Color.Chroma { return this.float(baseC, this.isLight(l) ? this.maxcLight : this.maxcDark); } dullFor(l: Color.Luma, baseC: Color.Chroma): Color.Chroma { return this.float(baseC, this.isLight(l) ? this.mincLight : this.mincDark); } analogous1(baseH: Color.Hue): Color.Hue { const size = this.float(this.minhSep, 2 * this.minhSep); return this.boolean() ? baseH + size : baseH - size; } analogous(baseH: Color.Hue, count: number): Color.Hue[] { const minWidth = min(count * this.minhSep, this.maxhWidth * 0.8); const width = this.float(minWidth, this.maxhWidth); const sep = width / (count - 1); const start = baseH - (width / 2); const numbers = Array.from({length: count}, (_u, i) => start + i * sep); return this.boolean() ? numbers : numbers.reverse(); } complementary1(baseH: Color.Hue): Color.Hue { return this.analogous1((baseH + 180) % 360); } complementary(baseH: Color.Hue, count: number): Color.Hue[] { const angle = this.float(180 - this.maxhCompl/2, 180 + this.maxhCompl/2); return this.analogous(baseH + angle, count); } triad(baseH: Color.Hue): [Color.Hue, Color.Hue] { const angle = this.float(120 - this.maxhTriad/2, 120 + this.maxhTriad/2); return [baseH - angle, baseH + angle]; } baseLuma(ld?: LightDark): Color.Luma { if (ld == 'light') { return this.float(this.minlLight, this.maxl); } else if (ld == 'dark') { return this.float(this.minl, this.maxlDark); } else { return this.float(this.minl, this.maxl); } } baseChroma(l: Color.Luma): Color.Chroma { if (l >= this.minlLight) { return this.float(this.mincLight, this.maxcLight); } else { return this.float(this.mincDark, this.maxcDark); } } baseHue(): Color.Hue { return this.float(360); } color(ld?: LightDark): Color.Color { const l = this.baseLuma(ld); const c = this.baseChroma(l); const h = this.baseHue(); return Color.oklch(l, c, h); } }