Baseline color rules working (#111)
Made the new rule the default, but its now easy to play with to adjust the exact color rule we are using. This new one pick 3 values (with a bit of randomness), picks a random hue (then picks 2 more with a small chance of taking the opposite hue), and picks a saturation level that tends to be high.pull/115/head
rodzic
bbc278d061
commit
7003daeebf
|
@ -1,13 +1,19 @@
|
||||||
import { Random } from "./random";
|
import { Random } from "./random";
|
||||||
import { range } from "./util";
|
import { range, clamp } from "./util";
|
||||||
import * as colorspaces from "colorspaces";
|
import * as colorspaces from "colorspaces";
|
||||||
|
import { ColorTuple, hsluvToHex } from "hsluv";
|
||||||
|
|
||||||
type RandomPaletteGenerator = (numEntries: number, rng: Random) => string[];
|
type RandomPaletteGenerator = (numEntries: number, rng: Random) => string[];
|
||||||
|
|
||||||
export type RandomPaletteAlgorithm = "RGB" | "CIELUV";
|
export type RandomPaletteAlgorithm =
|
||||||
|
| "RGB"
|
||||||
|
| "CIELUV"
|
||||||
|
| "threevals"
|
||||||
|
| "huecontrast"
|
||||||
|
| "randgrey";
|
||||||
|
|
||||||
export const DEFAULT_RANDOM_PALETTE_ALGORITHM: RandomPaletteAlgorithm =
|
export const DEFAULT_RANDOM_PALETTE_ALGORITHM: RandomPaletteAlgorithm =
|
||||||
"CIELUV";
|
"threevals";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clamp the given number to be between 0 and 255, then
|
* Clamp the given number to be between 0 and 255, then
|
||||||
|
@ -33,8 +39,8 @@ function createRandomRGBColor(rng: Random): string {
|
||||||
|
|
||||||
function createRandomCIELUVColor(rng: Random): string {
|
function createRandomCIELUVColor(rng: Random): string {
|
||||||
const max_luv_samples = 100;
|
const max_luv_samples = 100;
|
||||||
let luv_sample_failed = true;
|
let luvSampleFailed = true;
|
||||||
let rand_color_hex: string = "#000000";
|
let randColorHex: string = "#000000";
|
||||||
|
|
||||||
//See if we can pull out a sample inside the LUV solid
|
//See if we can pull out a sample inside the LUV solid
|
||||||
for (let i = 0; i < max_luv_samples; i++) {
|
for (let i = 0; i < max_luv_samples; i++) {
|
||||||
|
@ -42,29 +48,114 @@ function createRandomCIELUVColor(rng: Random): string {
|
||||||
let L = rng.inInterval({ min: 0, max: 100 });
|
let L = rng.inInterval({ min: 0, max: 100 });
|
||||||
let u = rng.inInterval({ min: -134, max: 220 });
|
let u = rng.inInterval({ min: -134, max: 220 });
|
||||||
let v = rng.inInterval({ min: -140, max: 122 });
|
let v = rng.inInterval({ min: -140, max: 122 });
|
||||||
let rand_color = colorspaces.make_color("CIELUV", [L, u, v]);
|
let randColor = colorspaces.make_color("CIELUV", [L, u, v]);
|
||||||
|
|
||||||
//console.log(`L:${L},u${u},v${v}`);
|
if (randColor.is_displayable() && !(L == 0.0 && (u != 0 || v != 0))) {
|
||||||
if (rand_color.is_displayable() && !(L == 0.0 && (u != 0 || v != 0))) {
|
randColorHex = randColor.as("hex");
|
||||||
rand_color_hex = rand_color.as("hex");
|
luvSampleFailed = false;
|
||||||
//console.log(rand_color_hex);
|
|
||||||
luv_sample_failed = false;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//just sample sRGB if I couldn't sample a random LUV color
|
//just sample sRGB if I couldn't sample a random LUV color
|
||||||
if (luv_sample_failed) {
|
if (luvSampleFailed) {
|
||||||
//console.log("Sampling sRGB");
|
|
||||||
let rgb = [0, 0, 0].map(
|
let rgb = [0, 0, 0].map(
|
||||||
() => rng.inRange({ min: 0, max: 255, step: 1 }) / 255.0
|
() => rng.inRange({ min: 0, max: 255, step: 1 }) / 255.0
|
||||||
);
|
);
|
||||||
//console.log(rgb);
|
let randColor = colorspaces.make_color("sRGB", rgb);
|
||||||
let rand_color = colorspaces.make_color("sRGB", rgb);
|
randColorHex = randColor.as("hex");
|
||||||
rand_color_hex = rand_color.as("hex");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rand_color_hex;
|
return randColorHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRandGrey(rng: Random): string[] {
|
||||||
|
let L1 = rng.inInterval({ min: 0, max: 100 });
|
||||||
|
let L2 = rng.inInterval({ min: 0, max: 100 });
|
||||||
|
let L3 = rng.inInterval({ min: 0, max: 100 });
|
||||||
|
|
||||||
|
let Ls = [L1, L2, L3];
|
||||||
|
|
||||||
|
let h = 0;
|
||||||
|
let Hs = [h, h, h];
|
||||||
|
|
||||||
|
let S = 0;
|
||||||
|
let Ss = [S, S, S];
|
||||||
|
|
||||||
|
//zip
|
||||||
|
let hsls = Ls.map((k, i) => [Hs[i], Ss[i], k]);
|
||||||
|
let hexcolors = hsls.map((x) => hsluvToHex(x as ColorTuple));
|
||||||
|
|
||||||
|
//scramble order
|
||||||
|
hexcolors = rng.uniqueChoices(hexcolors, hexcolors.length);
|
||||||
|
return hexcolors;
|
||||||
|
}
|
||||||
|
|
||||||
|
function create3HColor(rng: Random): string[] {
|
||||||
|
let L = rng.fromGaussian({ mean: 50, stddev: 20 });
|
||||||
|
|
||||||
|
let Ls = [L, L, L];
|
||||||
|
|
||||||
|
Ls = Ls.map((x) => clamp(x, 0, 100));
|
||||||
|
|
||||||
|
let h1 = rng.inInterval({ min: 0, max: 360 }),
|
||||||
|
h2 = 360 * (((h1 + 120) / 360) % 1),
|
||||||
|
h3 = 360 * (((h1 + 240) / 360) % 1);
|
||||||
|
|
||||||
|
let Hs = [h1, h2, h3];
|
||||||
|
|
||||||
|
let S = 100;
|
||||||
|
let Ss = [S, S, S];
|
||||||
|
|
||||||
|
Ss = Ss.map((x) => clamp(x, 0, 100));
|
||||||
|
|
||||||
|
//zip
|
||||||
|
let hsls = Ls.map((k, i) => [Hs[i], Ss[i], k]);
|
||||||
|
let hexcolors = hsls.map((x) => hsluvToHex(x as ColorTuple));
|
||||||
|
|
||||||
|
//scramble order
|
||||||
|
hexcolors = rng.uniqueChoices(hexcolors, hexcolors.length);
|
||||||
|
return hexcolors;
|
||||||
|
}
|
||||||
|
|
||||||
|
function create3VColor(rng: Random): string[] {
|
||||||
|
let lowL_Mean = 20.0,
|
||||||
|
medL_Mean = 40.0,
|
||||||
|
hiL_Mean = 70,
|
||||||
|
lowL_SD = 30.0,
|
||||||
|
medL_SD = lowL_SD,
|
||||||
|
hiL_SD = lowL_SD;
|
||||||
|
|
||||||
|
let Ls = [
|
||||||
|
rng.fromGaussian({ mean: lowL_Mean, stddev: lowL_SD }),
|
||||||
|
rng.fromGaussian({ mean: medL_Mean, stddev: medL_SD }),
|
||||||
|
rng.fromGaussian({ mean: hiL_Mean, stddev: hiL_SD }),
|
||||||
|
];
|
||||||
|
|
||||||
|
Ls = Ls.map((x) => clamp(x, 0, 100));
|
||||||
|
|
||||||
|
//Now we have 3 lightness values, pick a random hue and sat
|
||||||
|
|
||||||
|
let h1 = rng.inInterval({ min: 0, max: 360 }),
|
||||||
|
h2 = 360 * (((h1 + 60 * Number(rng.bool(0.5))) / 360) % 1),
|
||||||
|
h3 = 360 * (((h1 + 180 * Number(rng.bool(0.5))) / 360) % 1);
|
||||||
|
|
||||||
|
let Hs = [h1, h2, h3];
|
||||||
|
|
||||||
|
let Ss = [
|
||||||
|
rng.fromGaussian({ mean: 100, stddev: 40 }),
|
||||||
|
rng.fromGaussian({ mean: 100, stddev: 40 }),
|
||||||
|
rng.fromGaussian({ mean: 100, stddev: 40 }),
|
||||||
|
];
|
||||||
|
Ss = Ss.map((x) => clamp(x, 0, 100));
|
||||||
|
|
||||||
|
//zip
|
||||||
|
let hsls = Ls.map((k, i) => [Hs[i], Ss[i], k]);
|
||||||
|
let hexcolors = hsls.map((x) => hsluvToHex(x as ColorTuple));
|
||||||
|
|
||||||
|
//scramble order
|
||||||
|
hexcolors = rng.uniqueChoices(hexcolors, hexcolors.length);
|
||||||
|
return hexcolors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,11 +170,36 @@ function createSimplePaletteGenerator(
|
||||||
range(numEntries).map(() => createColor(rng));
|
range(numEntries).map(() => createColor(rng));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory function to make a random palette generator for a triad generator
|
||||||
|
*/
|
||||||
|
|
||||||
|
function createTriadPaletteGenerator(
|
||||||
|
createTriad: (rng: Random) => string[]
|
||||||
|
): RandomPaletteGenerator {
|
||||||
|
return (numEntries: number, rng: Random): string[] => {
|
||||||
|
let colors: string[] = [];
|
||||||
|
let n = Math.floor(numEntries / 3) + 1;
|
||||||
|
|
||||||
|
if (numEntries == 3) {
|
||||||
|
colors = colors.concat(createTriad(rng));
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < n; i++) colors = colors.concat(createTriad(rng));
|
||||||
|
colors = colors.slice(0, numEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
return colors;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const PALETTE_GENERATORS: {
|
const PALETTE_GENERATORS: {
|
||||||
[key in RandomPaletteAlgorithm]: RandomPaletteGenerator;
|
[key in RandomPaletteAlgorithm]: RandomPaletteGenerator;
|
||||||
} = {
|
} = {
|
||||||
RGB: createSimplePaletteGenerator(createRandomRGBColor),
|
RGB: createSimplePaletteGenerator(createRandomRGBColor),
|
||||||
CIELUV: createSimplePaletteGenerator(createRandomCIELUVColor),
|
CIELUV: createSimplePaletteGenerator(createRandomCIELUVColor),
|
||||||
|
threevals: createTriadPaletteGenerator(create3VColor),
|
||||||
|
huecontrast: createTriadPaletteGenerator(create3HColor),
|
||||||
|
randgrey: createTriadPaletteGenerator(createRandGrey),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RANDOM_PALETTE_ALGORITHMS = Object.keys(
|
export const RANDOM_PALETTE_ALGORITHMS = Object.keys(
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import { inclusiveRange, NumericInterval, NumericRange } from "./util";
|
import {
|
||||||
|
inclusiveRange,
|
||||||
|
NumericInterval,
|
||||||
|
NumericRange,
|
||||||
|
GaussianDist,
|
||||||
|
} from "./util";
|
||||||
|
|
||||||
export type RandomParameters = {
|
export type RandomParameters = {
|
||||||
modulus: number;
|
modulus: number;
|
||||||
|
@ -65,6 +70,19 @@ export class Random {
|
||||||
return this.next() * (max - min) + min;
|
return this.next() * (max - min) + min;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a number from the specified gaussian distribution
|
||||||
|
* from: https://stackoverflow.com/questions/25582882/javascript-math-random-normal-distribution-gaussian-bell-curve
|
||||||
|
*/
|
||||||
|
fromGaussian({ mean, stddev }: GaussianDist, nsamples = 6) {
|
||||||
|
let runtotal = 0;
|
||||||
|
for (var i = 0; i < nsamples; i++) {
|
||||||
|
runtotal += this.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (stddev * (runtotal - nsamples / 2)) / (nsamples / 2) + mean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a random item from the given array. If the array is
|
* Return a random item from the given array. If the array is
|
||||||
* empty, an exception is thrown.
|
* empty, an exception is thrown.
|
||||||
|
|
12
lib/util.ts
12
lib/util.ts
|
@ -37,6 +37,11 @@ export type NumericRange = NumericInterval & {
|
||||||
step: number;
|
step: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GaussianDist = {
|
||||||
|
mean: number;
|
||||||
|
stddev: number;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return numbers within the given range, inclusive.
|
* Return numbers within the given range, inclusive.
|
||||||
*/
|
*/
|
||||||
|
@ -50,6 +55,13 @@ export function inclusiveRange({ min, max, step }: NumericRange): number[] {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clamp a number between min and max
|
||||||
|
*/
|
||||||
|
export function clamp(x: number, min: number, max: number) {
|
||||||
|
return Math.max(min, Math.min(x, max));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an array containing the numbers from 0 to one
|
* Return an array containing the numbers from 0 to one
|
||||||
* less than the given value, increasing.
|
* less than the given value, increasing.
|
||||||
|
|
|
@ -4620,6 +4620,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz",
|
||||||
"integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg="
|
"integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg="
|
||||||
},
|
},
|
||||||
|
"hsluv": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hsluv/-/hsluv-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-ERcanKLAszD2XN3Vh5r5Szkrv9q0oSTudmP0rkiKAGM/3NMc9FLmMZBB7TSqTaXJfSDBOreYTfjezCOYbRKqlw=="
|
||||||
|
},
|
||||||
"html-comment-regex": {
|
"html-comment-regex": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz",
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"colorspaces": "^0.1.5",
|
"colorspaces": "^0.1.5",
|
||||||
"gh-pages": "^3.1.0",
|
"gh-pages": "^3.1.0",
|
||||||
|
"hsluv": "^0.1.0",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"parcel-bundler": "^1.12.4",
|
"parcel-bundler": "^1.12.4",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
|
|
Ładowanie…
Reference in New Issue