120 lines
3.5 KiB
TypeScript
120 lines
3.5 KiB
TypeScript
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);
|
|
}
|
|
}
|